The v0.9 milestone: payment processing and subscription management
(REQ-F-018 activated as REQ-9-001..005 per D-074). The billing module
(ai_service/billing/ — PaymentProvider protocol with deterministic
mock + Stripe-compatible REST over the shared pool, the 7th D-027
store with 7 tables, the HMAC-SHA256 webhook core per D-077), the
seeded catalog + /v1/billing routes, the pricing page's runtime
conversion, posting entitlement enforcement with honest 402 upgrade
CTAs, and the full subscription lifecycle with a queryable audit
trail. Operator-mediated checkout at pilot (D-076); grants exactly-
once per session (G-303); webhook state resolved from local rows only
(G-302); consumption as a single conditional UPDATE (G-304). Tags
v0.8.0..v0.8.6 on the v0.8.x line; the tag lane built every release's
full asset matrix + registry image (D-064).
---ci---
phase: 6
milestone: v0.9
status: complete
requirements:
covered: [REQ-9-001, REQ-9-002, REQ-9-003, REQ-9-004, REQ-9-005]
partial: []
---/ci---
CT 113 root password + the org runner registration token are retained for
future use per founder directive (rotation declined; supersedes the G-206
rotation request). Both live only in .ciagent/.env.secrets (gitignored,
0600 — verified the sole copies tree-wide). The live-host deploy key
(nc_ci_deploy -> root@10.0.0.105) is persistent per founder approval.
Release notes updated to match.
---ci---
phase: post-milestone
milestone: v0.8
status: complete
decision: D-073
---/ci---
The v0.8 milestone: installable on any Debian-based server via docker
(Gitea registry image, honest sandbox degrade), deb (amd64+arm64,
advisory postinst), and arch-aware binary — with release consistency
enforced by pre-commit hooks + Gitea Actions (3-runner fleet on CT 113,
machine-made releases with the full 8-asset matrix + registry image), the
CLI carrying the full suite (guide, channel-aware doctor), and the admin
runbook (deploy/README.md, evidence-backed recipes).
---ci---
phase: 6
milestone: v0.8
status: complete
requirements:
covered: [REQ-8-001, REQ-8-002, REQ-8-003, REQ-8-004, REQ-8-005]
partial: []
---/ci---
Escalation note: merge_to_main hook — proceeding per full autonomy +
founder report (deploy on the host reported success without updating;
the fix makes the failure loud and adds the freshness gate).
---ci---
phase: hotfix
milestone: v0.6
status: complete
type: hotfix
---/ci---
P1-1 WS ingest auth (grading DoS): the ingest socket is a grading input,
LAN-reachable in network mode, and identity params were self-asserted —
anyone could poison or flood a learner's trace. New HMAC ingest token
(telemetry/tokens.py): the sandbox manager mints it into NC_INGEST_URL
(bound to learner+task+sandbox, length-prefixed binding — no delimiter
injection), the WS endpoint verifies before accept. Mint/verify share one
app.state instance (per-boot random secret when
AI_TELEMETRY_INGEST_SECRET is unset; env-set for multi-process deploys).
Untokened/bad/cross-identity dials close 1008 storing nothing.
P1-2 defense-id collision: `dfn-{ms:x}` collided on same-millisecond
starts — the insert-only store's PK then 500'd the second start
permanently (the identity-resubmit P0-C1 class). Nonce+counter mint
(the identity/mock.py pattern); ids stay opaque, PII-free.
P1-3 browser-mode SR/TTS client gap: the mode="browser" descriptor has
advertised client-native SpeechRecognition + speechSynthesis since v0.5
(D-030/CUT-1) — the client never implemented it, so browser-mode voice
posted audio the server had no provider to transcribe. New pure module
apps/web/lib/browser-voice.ts (recognizeOnce + speakText, injectable
seams); the defense session uses it when descriptor.mode === 'browser'
(text answers; examiner questions spoken via speechSynthesis), keeps
MediaRecorder upload for server/mock modes, honest 'unsupported' state.
P1-4 browser-mode audio coverage: 8 node:test pins for the module
(transcript roundtrip, silent-user, permission/unsupported error maps,
timeout overrun, TTS absent/empty/cancel-prior/resolve-on-end).
488 backend tests (+10); web 22/22 (+8); CLI 61/61; typecheck 8/8;
ruff clean; export builds.
---ci---
phase: hotfix
milestone: v0.5
status: complete
type: hotfix
---/ci---
The host dogfood run, bug three: pnpm came from PNPM_HOME while node
lives in nvm — the prefix carried only pnpm's dir, so `pnpm build`
spawned turbo, which exec'd bare `node` and died rc 127. The
discovered prefix now includes every candidate dir carrying node OR
pnpm (pnpm's own dir first), so pnpm children resolve the same node
family on split installs — the exact production-host layout.
CLI 61/61 (+1 split-install regression pin).
---ci---
phase: hotfix
milestone: v0.5
status: complete
type: hotfix
---/ci---
The host dogfood run caught the second first-run bug: `git checkout
origin/main` detaches HEAD, and `git pull --ff-only` refuses to run on a
detached HEAD — deploy dead-ended on the production clone. origin/main
now resolves through local main (checkout main; checkout -B main
origin/main when absent), pulls only on that branch; --ref tags are
immutable checkouts (no pull attempted).
CLI 60/60 (+1 detached-HEAD regression).
---ci---
phase: hotfix
milestone: v0.5
status: complete
type: hotfix
---/ci---
Two first-run bugs found deploying v0.4.6 to the host (the dogfood run):
- run() filtered flags EVERYWHERE, so `deploy --service` never reached
the command ("unknown flag: --service" + help) — the exact command the
installer tells the user to run. Global flags are now only those
BEFORE the command name; subcommands own their own flags. doctor and
bootstrap gained arg validation (they previously ignored unknown
flags silently) to keep the exit-2 contract the old dispatcher gave.
- verify's web-export check failed fresh DEV clones (bootstrap → verify
→ exit 1: no export until pnpm build, by design in dev mode). Now
warn-only when absent; the runtime catch is the live mount probe +
the loud boot warning. Deploy builds the export before verify runs,
so single-port deploys keep the hard guarantee.
CLI 59/59 (fresh-clone E2E green against the rebuilt SEA binary).
---ci---
phase: hotfix
milestone: v0.5
status: complete
type: hotfix
---/ci---
The public site was down for three releases: the domain root served the
API's JSON 404 because the deployed service ran mount-less — the web
export was never built on the host (silent dev.ts auto-wire skip) and
nothing detected it. This hotfix fixes the class:
- nextcraft deploy: clone/update → install → venv → build export →
restart → verify, one idempotent command; --service installs a
systemd unit for reboot persistence.
- toolchain discovery: pnpm behind nvm/PNPM_HOME/corepack is found —
the host had pnpm all along, non-interactive shells just couldn't
see it.
- loud mount-less boot warning + verify's live mount probe (GET / must
be HTML, not JSON 404) — the outage can no longer ship silently.
Escalation note: merge_to_main hook — proceeding per full autonomy +
founder directive (troubleshooting round + patch release requested
2026-09-21, dev-experience fixes for the one-liner incorporated).
---ci---
phase: hotfix
milestone: v0.5
status: complete
type: hotfix
---/ci---
The domain root (nextcraft.coreci.dev) served the API's JSON 404 for three
releases. Root cause (probe-verified on the host): the deployed uvicorn ran
without AI_WEB_STATIC_DIR because apps/web/out was never built — dev.ts
auto-wiring silently skips when the export is absent, and NOTHING detects
the mount-less boot. HAProxy was never the problem; the JSON 404 at / IS
the mount-less signature (pinned by test_web_static.py since v0.3.6).
Class fixes (the deploy experience, per founder directive):
- toolchain discovery (lib/toolchain.ts): node/pnpm behind nvm,
PNPM_HOME, or corepack are FOUND even when non-interactive shells
can't see them — the host had pnpm the whole time; doctor, bootstrap
and deploy all resolve through it (PATH → nvm → PNPM_HOME → corepack).
- nextcraft deploy: the one command the installer hands off to —
clone-or-update → pnpm install → venv → web export build → restart →
verify. Fails LOUDLY if the site isn't actually served. Idempotent:
same command for first install and upgrades. deploy --service installs
a generated systemd unit (real paths, Restart=always, enabled) so the
stack survives reboots; systemd becomes the supervisor (dev -d and
stop respect it).
- create_app(): a mount-less boot now WARNs naming the fix (G-11
doctrine — the failure was silent for three releases).
- verify: web-export presence check + LIVE MOUNT PROBE — when :8420 is
serving, GET / must return HTML, not the API's JSON 404. This is the
check that would have caught every prior outage.
- install.sh next-steps → `nextcraft deploy --service`; README +
deploy/README rewritten around the one-command flow.
478 backend tests (+1 loud-boot pin); CLI 59 (45→59: toolchain discovery
x7, deploy x4, verify probes x3, hermeticity fixes); web 14/14;
typecheck 8/8; ruff clean; static export builds.
---ci---
phase: hotfix
milestone: v0.5
status: complete
type: hotfix
---/ci---
P0-C1: mock identity ids were hash(learner, DOB)-deterministic — resubmit
after a TERMINAL verdict collided on the insert-only PK → unhandled 500,
permanently blocking re-verification (the D1 lockout one path deeper).
Ids now mint uniquely per submit (nonce + counter, PII-free); verdict
determinism preserved in poll(). Regression: resubmit-after-reject → 200
fresh pending → G-13 409.
P0-C2: defense audio answers 500'd on every default deployment — the mock
provider's empty transcript queue raised MockVoiceFailure (unhandled) the
first time a learner used the mic; same for a real endpoint outage.
transcribe() now maps provider RuntimeErrors to honest 502s (house
pattern); typed answers unaffected. Regression: unscripted mock → 502,
then typed answer → 200 on the same defense.
P0-C3: v0.5's variant_record columns brick a pre-v0.5 DB (create_all
never ALTERs) — every variant read/write raised OperationalError after an
in-place upgrade. Idempotent _ensure_v05_columns() backfill in the store
constructor (PRAGMA table_info → ALTER ADD COLUMN with defaults; legacy
rows read as build-kind). Regression: literal v0.4 table → open → read →
write → reopen idempotent. deploy/README documents the in-place note
(fresh-state directive still honored).
477 backend tests (+3); ruff clean; typecheck 8/8.
P1 flags recorded for pre-public hardening (non-blocking): unauthenticated
WS ingest on LAN (grading DoS), browser-mode SR/TTS client gap, defense-id
ms collision, browser-mode audio E2E.
---ci---
phase: 5
milestone: v0.5
status: review
---/ci---