Skip to content

[DOCS] Reset the trackers to the tree, and add a checked ROADMAP.md - #422

Open
justin13888 wants to merge 11 commits into
chore/merge-v1-head-397from
docs/reset-trackers-and-add-roadmap-398
Open

[DOCS] Reset the trackers to the tree, and add a checked ROADMAP.md#422
justin13888 wants to merge 11 commits into
chore/merge-v1-head-397from
docs/reset-trackers-and-add-roadmap-398

Conversation

@justin13888

@justin13888 justin13888 commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Description

Resets SLICES.md and the design docs to what the merged tree actually holds, and adds a root ROADMAP.md — the package-level view — behind a new docs-truth gate that resolves every row against the manifests themselves.

Summary

  • ROADMAP.md (new): one row per package across every toolchain — 47 today: 11 cargo, 2 gradle, 24 tuist, 1 SwiftPM, 2 bun, the locales/ catalog, capsule-vision, the rawshift submodule and the four legacy-review/ buckets. Columns are Package · Kind · Owns · State · Gate · Owner docs · Open slices · Next milestone · Notes, with a closed seven-state set defined at the top and a deferred register below the table. SLICES.md stays the slice-level tracker; ROADMAP.md never restates a slice's status.
  • capsule-docs/scripts/check-roadmap.mjs (new, plus 32 unit tests): a fourth docs-truth check. Node builtins only, no toolchain, matching the three existing checks. It parses Cargo.toml, settings.gradle.kts, Project.swift, the Package.swift/package.json/pyproject.toml package roots, locales/, .gitmodules and legacy-review/* at run time, so adding a package fails the gate until ROADMAP.md gains a row. It also fails on an orphan row, an unknown state, a slice id with no detail block in SLICES.md, a Gate naming a task mise does not have, a row whose Kind disagrees with the manifest that declared it, a duplicate row, and a malformed row.
  • SLICES.md: the Baseline and Sequencing prose reset to the merged tree, the index preamble and row-count recount (205 rows; 87 ACTIVE / 75 RETIRED / 43 MIXED; 98 done / 57 done* / 28 ready / 9 part / 9 blocked / 4 post-v1), the gates-table row for the four spargen::omit! operations rewritten to the real application/cbor reason, and corrections on thirteen named rows: S-D1/S-D2/S-D7/S-D8/S-D10 RETIRED | readyMIXED | done (both items the tracker owed capsule-sdk landed), S-D26 and S-I5done, S-Z9 unblocked, S-B1/S-B5/S-B13blocked on an unconsumed Rawshift, S-B14's missing owed producer, and S-D12's misplaced cadence scheduler.
  • Design docs: the stale capsule-sdk/openapi.json citations, the "REST is blocked" paragraph, the flat-blob-store claim, capsule-core::crypto::mls, the licensing.md tense, and one normative sentence in module-map.md for ADR-0005.
  • ADRs 0004–0006: capsule-wire's retirement, the single uniffi namespace pair an app links, and the server's runtime shape.

Validation

Every command run inside the lane worktree. Round 2 at 09b95705, after the review
repairs and the merge of the moved base (chore/merge-v1-head-397 at 99dd4bc8, a
capsule-web/bun.lock regeneration; merge commit 0fe7e290, no conflicts).

Command Outcome
git merge chore/merge-v1-head-397 pass — merge commit, one file, no conflicts
mise run check-docs-truth passcross-links: 529, endpoint-census: 84, module-paths: 120, roadmap: 47 package(s) checked, all rows resolve. States defined and unused: frozen, blocked
bun run test in capsule-docs / mise run test-docs pass — 107 tests, 6 files (42 in check-roadmap.test.mjs, 10 of them new this round)
mise run lint-check-docs pass
mise run lint-check-md pass — 172 files, 0 issues
mise run check-docs pass — format, lint, tests, Astro build: 59 pages, "All internal links are valid"
grep -c '^### S-' SLICES.md 205, matching the recounted index

mise run check-rust was not re-run: this round changed only Markdown and
capsule-docs/scripts/*.mjs, no Rust source or doc comment. Its round-1 result stands.

09b95705 shrinks the ACTIVE-legend repair to the two words it needs. That cell sits one
line above the RETIRED cell #431 rewrites when the core-import-media bucket goes, and the
longer wording had grown a forward reference into exactly the text that PR replaces. All four
docs gates were re-run after it and pass unchanged.

Not touched, deliberately, for the branches stacked on this one: the S-C27 index row and
its ### S-C27 detail block are byte-identical to the base (git show 99dd4bc8:SLICES.md);
#430 owns them. The repair round touches neither the intro legacy-review/ buckets paragraph
nor the RETIRED Area cell, both of which #431 edits.

Round 1 at 1043a414 — all pass: mise run check-docs-truth (with the negative check:
delete the capsule-wasm row, mistype one Gate task, mistype one slice id → three
findings, exit 1; restored, exit 0), mise run test-docs, lint-check-docs,
lint-check-md, check-docs, mise run architecture-check ("boundaries are intact"),
mise run check-rust (fmt, clippy, i18n-check, i18n-guard, openapi-check-kynos,
architecture-check, license-check, translate-readme-check, build-rust, build-check-wasm,
build-ffi, lint-check-ffi, gen-bindings, verify-examples), mise run test-rust (160 run,
160 passed, 0 skipped). CI was green on every required job.

Not run, and why:

Gate Classification Why
mise run check-swift unavailable macOS only; mise-tasks/check-swift exits 0 with "Skipping swift checks" on this Linux host. No Swift file changed.
mise run check-web not applicable The only capsule-web/ change is the base's own bun.lock, carried in by the merge.
mise run check-kotlin / check-vision not applicable No file under either tree changed.

i18n-guard prints an informational list of 56 error codes the Swift client names nowhere;
it is pre-existing, unrelated to this change, and does not fail the task.

Risks and rollout

  • Documentation and one Rust doc-comment sweep only. No behaviour changes; no generated artifact is hand-edited.
  • check-roadmap's oracles are regex over manifest text, not parsers, which is what keeps docs-truth toolchain-free. A conditional declaration is therefore invisible to them: Project.swift's ffiEnabled ? … ternary still matches module(", so CapsuleCatalogFFI is required to have a row (marked excluded), while settings.gradle.kts's commented-out :cli/:desktop match nothing and must not have one. Both cases are pinned by unit tests.
  • Gate hole, not introduced here but exposed by it: the CI docs-truth paths filter (.github/workflows/ci.yml:77-90) does not list Cargo.toml, settings.gradle.kts, capsule-swift/Project.swift or the package roots, so a pull request that adds a package without touching any .md will not run the roadmap check in CI. ci.yml is outside this lane's manifest; filed on ci: close the gate holes — test job outside required, missing path filters, pre-push parity, and the Kotlin smoke test #416's territory and named in the report rather than edited here.

Related Issues

Refs #398. Stacked on #418 (chore/merge-v1-head-397), which is this PR's base.
Refs #421 — the README half of #398, split out (decision 1).
Refs #423 — deleting legacy-review/core-import-media, deferred on a manifest boundary (decision 9).

Contributor Checklist

  • I agree to the Contributor License Agreement for this and future contributions.
  • My code follows the project's style guidelines according to CONTRIBUTING.md.
  • Tests pass
  • No sensitive info / secrets
  • Docs updated if needed

Decisions taken

  1. Deliverable boundary
    Taken: Everything except the README rewrite and its translations - ROADMAP.md, check-roadmap.mjs, the SLICES.md reset, the confirmed design-doc corrections, the core-import-media deletion, and ADRs 0004-0006. Manifest excludes README.md, README..md x12 and xtask/translations/readme/.json.
    Rejected: The whole issue including the README - xtask/src/translate_readme.rs:543-549 makes FileBackend bail when a block's source no longer matches the README, so mise run translate-readme cannot regenerate an edited README; it needs translate-readme-extract plus authored target text in 12 locales, and until that lands mise run translate-readme-check inside check-rust (mise.toml:106) is red for every branch in the programme.
    Rejected: SLICES.md corrections only, deferring ROADMAP.md - the issue's title deliverable is ROADMAP.md and it has no dependency on the README half.
    Reverses: Add the README slice, run mise run translate-readme-extract, fill xtask/translations/readme/
    .json, run mise run translate-readme.
    Filed: the lane files "docs: honest README status paragraph + ROADMAP link, with the 12 regenerated translations" and links it from the PR.

  2. ROADMAP row granularity for the Apple client
    Taken: One row per Tuist target - 22 module() targets, CapsuleCatalogFFI (excluded, TUIST_FFI=1 only), and the Capsule app target = 24 rows; check-roadmap's Swift oracle regexes module("Name" plus appTarget's name:.
    Rejected: One capsule-swift row - the issue asks for one row per package across every toolchain including tuist targets, and capsule-swift/Modules/ holds 23 independently-tested framework targets (Project.swift:103-133); collapsing them empties the Open slices column for lane U's 23 slices.
    Rejected: Grouped rows - a group has no single Gate or State.
    Reverses: Replace the 24 rows with one capsule-swift row and drop the Project.swift oracle from check-roadmap.mjs.

  3. What check-roadmap resolves package names against
    Taken: The manifests themselves, parsed at run time by regex over Cargo.toml, settings.gradle.kts, Project.swift, the two package.json files, locales/ and legacy-review/*/ - adding a package fails the gate until ROADMAP.md gains a row; no new committed data file; node builtins only.
    Rejected: A committed package list - tautological: both hand-edited, so a new package is absent from both and the gate passes.
    Rejected: An xtask subcommand in check-rust - docs-truth.mjs:20-33 keeps these checks toolchain-free so docs-only PRs pay no cargo build.
    Reverses: Add capsule-docs/roadmap-packages.txt, read it in checkRoadmap, delete the five parsers.

  4. ADR-0005's status and Contract line
    Taken: Status accepted with Contract pointing at capsule-docs/src/content/docs/design/module-map.md, plus one normative sentence in module-map.md's Client Boundaries table stating that an app links exactly the capsule_core_ffi + capsule_sdk namespace pair.
    Rejected: Status proposed with no Contract line - the decision has landed (capsule-core-ffi/src/lib.rs:32-38, mise-tasks/gen-bindings:23-30); adr/README.md:92 reserves proposed for decisions not landed anywhere.
    Rejected: Contract pointing at developer-docs.md:151 - a directory listing, not a normative statement.
    Reverses: Set Status to proposed, delete the Contract line, revert the module-map.md sentence.

Note for ADR-0004 and ADR-0006: Status proposed, no Contract line (neither has landed; issues #400 and #401 land them).

Amendment (orchestrator, before launch): the S-C27 row/detail note is OWNED BY LANE #400 (capsule-wire retirement), not this lane; do not touch S-C27. ADR-0004 is written here as Status: proposed with no Contract line; lane #400 flips it to accepted when the crate actually retires.

Decisions taken inside the manifest, in the same shape

  1. capsule-core-swift gets a ROADMAP row, and swiftpm joins the Kind set
    Taken: A 47th package. capsule-core-swift/Package.swift declares a real SwiftPM package (the Secure Enclave signer and its software fallbacks); mise-tasks/lint-check-swift:6 loops over it, so mise run check-swift is its gate, and mise-tasks/test-swift drives only the Tuist workspace, which the Notes cell says.
    Rejected: The plan's 45-row list, which had no row for it - the check-roadmap oracle finds it from its own manifest, so omitting it fails the gate. A plan count is not evidence; the tree is.
    Rejected: Folding it under capsule-swift - capsule-swift is not itself a package row, only its Tuist targets are, and this package is built by a different toolchain.
    Reverses: Delete the row, drop Package.swift from manifestDirs, and drop swiftpm from the Kind vocabulary.

  2. A defined state no row uses is reported, not failed
    Taken: checkRoadmap returns the unused states and reportRoadmap names them in its line; the gate stays green. No package in the tree is honestly frozen today, and frozen is one of the seven states the issue mandates.
    Rejected: The plan's "a state defined at the top that no row uses" as a hard finding - it puts steady pressure on whoever edits the file to give the spare term a row, which is a dishonest state assignment, the exact defect this check exists to prevent.
    Rejected: Dropping frozen from the vocabulary - the issue names the seven states, and a state set that shrinks whenever it is briefly unused stops being a closed set.
    Reverses: Push the unused states into findings and delete the unused field.

  3. The excluded state is defined as "outside the shipped build", not "outside every gate"
    Taken: capsule-vision is excluded with gate mise run check-vision, and the Notes cell says the gate is format and lint only. The plan's wording ("outside every gate") would have made that row self-contradicting.
    Rejected: excluded with gate - false; mise.toml:111-113 runs format and lint over it.
    Rejected: stabilizing - the state means "live and inside a gate with the contract still moving"; research notebooks that ship in no client are not that.
    Reverses: Restore the narrower definition and set the row's gate to .

  4. The deferred register is a second table, not prose
    Taken: A four-column register below the package table, so deferred has checkable users and the package table stays exactly nine columns. The check finds the package table by its Package header and collects state usage from every table with a State column.
    Rejected: The plan's "single pipe table" with a bullet-list register - the state-usage scan would then have to guess at prose, and a bullet list is not a row anyone can resolve.
    Reverses: Collapse the register into a bullet list and scan the whole document body for backticked state names.

  5. legacy-review/core-import-media is described honestly rather than deleted
    Taken: The tracker now says the bucket exists, why it is a stale twin, and which issue removes it; the directory stays and ROADMAP.md carries a review-only row for it. Filed as cleanup: delete legacy-review/core-import-media, the stale twin S-C59 recorded as gone #423.
    Rejected: Deleting it here - the deletion dangles two present-tense citations in capsule-docs/src/content/docs/design/import/pipeline.md (:54, :129, both "the old plaintext decode/extract path is review-only under legacy-review/core-import-media/") and one in SLICES.md:708, and pipeline.md is outside this lane's manifest. The run's manifest-discipline gate says stop and report a revised manifest rather than widen.
    Rejected: Leaving the prose claiming the bucket is gone - that is the class of defect docs: reset the tracker and design docs to what the tree holds, and add ROADMAP.md #398 exists to remove, and the prose head is inside the manifest, so correcting it costs nothing.
    Reverses: git rm -r legacy-review/core-import-media, repair the three citations, drop the ROADMAP.md row, and record the deleting sha in the S-C59 note - the whole of cleanup: delete legacy-review/core-import-media, the stale twin S-C59 recorded as gone #423.

  6. Five SDK rows change Area, not only Status
    Taken: S-D1, S-D2, S-D7, S-D8, S-D10 go RETIRED | readyMIXED | done (S-D8 done*). SLICES.md's own Area legend says RETIRED means the target sits in a legacy-review/ bucket, and Sequencing says the SDK rows carry it because the crate's wire contract was being re-sourced. Both of the items that section owed have landed - capsule-sdk/build.rs reads capsule-server/openapi.json, sync.rs drives GET /v1/sync through the generated client, tonic/tonic-prost/prost are out of the manifest, and no retired package is a dependency - so the marking has no basis left.
    Rejected: The issue's own remedy, flipping them readydone and leaving Area alone - SLICES.md:57-62 says done is not available on a RETIRED row and that an implemented RETIRED slice reverts to ready. That remedy asks for a state the file's own legend forbids.
    Rejected: Leaving all five untouched because the legend makes ready correct - true only while RETIRED is correct, and it is not.
    Rejected: Extending the same correction to S-D9 and S-D17 - the same reasoning applies to them, and neither is a row this lane's manifest names. Named in the unresolved-notes section below instead.
    Reverses: Restore RETIRED | ready on all five and drop the correction notes from their detail blocks.

  7. ADR-0006's filename says "three adapters per port", not "three adapter pairs"
    Taken: adr/0006-the-server-is-a-binary-with-config-and-three-adapters-per-port.md. capsule-server/src/store/mod.rs declares six typed stores - AuthStateStore, UploadSessionStore, CohortStore, and the three ceremony stores - and its own comment says three adapters are planned per port.
    Rejected: The filename the plan gave - "three adapter pairs" describes neither the count nor the shape, and an ADR whose title miscounts the thing it decides is the kind of record this directory exists to stop producing.
    Reverses: Rename the file and reword the title.

  8. A MIXED remainder with a named live home is done*, not done
    Taken: S-D1 and S-D2 gain Owed → S-Q1 (E2E cases 2 and 3, e2e: land the bounded E2E cases whose server legs now exist (cases 1, 2, 3, 6, 7, 9, 12, 13) #409); S-D7 gains Owed → S-D17 (sdk: recovery calls a route the contract does not serve, and three smaller gaps #408). The MIXED rule at the top of SLICES.md is narrowed: the retiring half is "owed by construction" only where no slice carries it, and where a named slice does, the row is done* and Owed → points at it.
    Rejected: Leaving all three done - S-D8 already records the identical remainder (S-D17) as done* with that pointer, so one remainder would be read two ways in one table, three rows apart.
    Rejected: Dropping the Owed → pointers from S-D8 instead, for consistency downward - it would delete the only navigation a reader has from a landed row to the work still outstanding against it.
    Reverses: Restore done on the three rows, delete their Owed → cells and the two done* notes, and restore the original sentence at SLICES.md:58.

  9. Slice-id resolution covers every cell of a row, and the whole document
    Taken: One pass over every backticked S-… in ROADMAP.md, reported once with its line; the Open slices column keeps only its shape check. citedSlices is exported and unit-tested.
    Rejected: Validating the Open slices column alone - the four prose defects the review found (capsule-wire's Notes, capsule-wasm's Owns, capsule-server's Notes, xtask's Owns) all sat in columns nothing checked, and Next milestone and Notes cite slices constantly.
    Rejected: Adding a path-citation discipline to Notes as well - that is a second module-paths check, for a file that cites few paths, and it would fail on the prose a Notes cell is for.
    Reverses: Restrict the regex to the Open slices column and delete citedSlices.

  10. Package-root discovery recurses one level, and the Gradle regex matches argument lists
    Taken: manifestDirs descends one level (pruning hidden and skipped directories, and never searching inside a package root for a second one) and names a nested root repo-relative; the Gradle oracle matches include( argument lists, so include(":a", ":b") and include (":c") both resolve. The promise at ROADMAP.md:14-20 therefore holds as written.
    Rejected: Documenting the blind spot and keeping root-only scanning - a gate that cannot catch the change it exists to catch is worse than no gate, because the file it guards reads as guarded.
    Rejected: An unbounded walk - it would read node_modules, target and .build on every docs-only pull request, against docs-truth's stated no-toolchain, cheap-to-run contract. The one-level bound is documented as a bound in the module comment.
    Reverses: Restore root-only scanning, restore the single-argument include regex, and document the blind spot instead.

Unresolved review notes

All findings from the orchestrator review are closed in f0d5c3cc, adea7c2c and
46d5559f. What remains open is what was open before it, plus two items the repairs
surfaced.

  • Revised manifest. Delivering the legacy-review/core-import-media deletion needs capsule-docs/src/content/docs/design/import/pipeline.md (two sentences) and SLICES.md:708, neither of which this lane's manifest lists. Per the run's manifest-discipline gate the lane stopped rather than widening: the deletion is not in this PR, the prose says so, and cleanup: delete legacy-review/core-import-media, the stale twin S-C59 recorded as gone #423 carries the whole of it with the exact edits enumerated.
  • CI gate hole. The docs-truth paths filter (.github/workflows/ci.yml:77-90) lists **/*.md and capsule-*/src/** but not Cargo.toml, settings.gradle.kts, capsule-swift/Project.swift, or the package.json/Package.swift/pyproject.toml package roots. A pull request that adds a package without touching Markdown will not run the roadmap check in CI. The gate itself now catches such a package (decision 14); the filter still decides whether it runs. ci.yml is outside this lane's manifest; ci: close the gate holes — test job outside required, missing path filters, pre-push parity, and the Kotlin smoke test #416 owns that file.
  • S-D9 and S-D17 keep RETIRED | ready. The Area argument in decision 10 applies to them identically — the SDK is not review material and its contract is re-sourced — but neither row is named in this lane's manifest. Whoever takes sdk: recovery calls a route the contract does not serve, and three smaller gaps #408 should correct them in the same direction or say why not.
  • Two serve-api citations left standing. SLICES.md:5059 (inside S-P8) and :5418 (inside S-U19) still name mise run serve-api, a task that retired with the Salvo binary. Both sit in slice detail blocks this lane's manifest does not name — lane P and lane U own them.
  • capsule-sdk/build.rs still says "OpenAPI 3.1" in the doc comment on build_rest_client, while the document it reads is 3.2. Outside this lane's manifest (which names four doc comments, all in capsule-server, plus one in capsule-cli).
  • frozen and blocked are defined states no row uses. Reported in the roadmap check's success line rather than failed, for the reason in decision 6. blocked joined the list when capsule-android moved to stabilizing: after that correction, no package in the tree is gated by a dependency outside itself, and none is frozen.
  • capsule-core-kotlin's row does not mention that its smoke test is red. Android build is red: capsule-android references a DI layer that is not in the tree #389's second compile failure is SoftwareSignerSmokeTest.kt calling FfiWorkspace.create without the client parameter the regenerated binding added. The row's Notes says "smoke tests only" and its Next milestone is StrongBox on a device runner, both of which read as healthier than the tree is. Not one of the review's findings and the instruction was to change nothing else, so it is recorded here rather than edited; Android build is red: capsule-android references a DI layer that is not in the tree #389 covers the repair.

`SLICES.md` tracks slices and nothing tracks packages, so "what state is
`capsule-server` in" has had no answer that could be checked. `ROADMAP.md`
gives every package the repository declares one row, with a state drawn from
a closed set defined at the top of the file.

The row is only worth reading if it cannot quietly fall behind, so
`docs-truth` gains a fourth check that resolves every row against the
manifests themselves rather than against a second committed list — a
committed list is tautological, since both files are hand-edited and a
package added to neither passes. Adding a package now fails the gate until
this file gains a row: 47 today, across cargo, gradle, tuist, SwiftPM, bun,
the catalog, uv, the submodule and the review buckets.

`capsule-core-swift` is a package the plan for this change missed. It carries
its own `Package.swift`, `lint-check-swift` loops over it, and `test-swift`
does not — which the row says, because a gate that covers format and lint
only is not the same claim as a gate.

A state defined and used by no row is reported in the success line rather
than failed. Failing it would put steady pressure on whoever edits the file
to give the spare term a row, which is a dishonest state assignment — the
exact defect this check exists to prevent.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 2, 2026

Copy link
Copy Markdown

Deploying capsule with  Cloudflare Pages  Cloudflare Pages

Latest commit: 09b9570
Status: ✅  Deploy successful!
Preview URL: https://5e4e3d3b.capsule-22k.pages.dev
Branch Preview URL: https://docs-reset-trackers-and-add.capsule-22k.pages.dev

View logs

The Baseline and Sequencing sections described the workspace as it was
before `S-C59`, and in three places contradicted the rest of the same file.

- Sequencing said the Salvo tree was still live, that `architecture-check`
  was reporting-only with 63 violations, and that Kynos was a git dependency
  pinned at rev `6513109`. `mise run architecture-check` runs inside
  `check-rust` and prints "boundaries are intact"; `Cargo.toml` takes
  `kynos = "0.1.0"` from crates.io; the gates table two hundred lines below
  already said so.
- It owed `capsule-sdk` two things — the gRPC sync half re-fronted on REST,
  and a schema sourced from Kynos. Both landed: `sync.rs` drives
  `GET /v1/sync` through the generated client with `tonic` and `prost` out
  of the manifest, and `build.rs` reads `capsule-server/openapi.json`.
- Baseline presented the retired Salvo `capsule-api` as what ships, and
  claimed the CLI covers E2E cases 1-3. `capsule demo` is offline by design
  and the networked commands have no server to reach; the cases are owed to
  `S-Q1`.
- The prose said the `core-import-media` bucket was gone. `S-C59` recorded
  that decision and did not carry it out — the directory is in the tree, so
  the prose now says so and names the issue that removes it.
- `capsule-core::exif` was called `RETIRED` in the Baseline and again in
  `S-B6`. It is live in `capsule-core/src/exif/`.
- The index preamble counted 141 slices. There are 205 rows and 205 detail
  blocks, 23 of them lane U.
- The gates table described the four `spargen::omit!` operations the Salvo
  document forced. Kynos makes both of those defects unrepresentable and all
  four generate; the four narrowed today are the `application/cbor` ones,
  narrowed because spargen's `classify_media` has no such media type.
Each of these is a row the tree already disproves.

`S-D1`, `S-D2`, `S-D7`, `S-D8`, `S-D10` read `RETIRED | ready`. `RETIRED`
never meant "capsule-sdk is review material" — Sequencing says the SDK rows
carry it because the crate's wire contract was being re-sourced. That
re-source landed: `build.rs` generates from `capsule-server/openapi.json`,
`sync.rs` drives `GET /v1/sync` through the generated client, `tonic` and
`prost` are out of the manifest, and no retired package is a dependency. The
five are `MIXED | done` (`S-D8` `done*`, its 401-retry-once still owed to
`S-D17`): the client half ships and the server half is the one being rebuilt.

`S-D26` and `S-I5` landed and read `ready`. `remote.rs` writes the rotated
pair back through `checkpoint` on every exit path; `84f719f6` added the
fifteen `cli.import.*` keys and put `capsule-cli/src` in `i18n-guard`'s
scope.

`S-Z9` read `blocked` on a Kynos document that exists: `gen_openapi.rs`
emits fifty-nine operations at OpenAPI 3.2 and `openapi-check-kynos` gates
them inside `check-rust`. Only the docs work is left.

`S-B1`, `S-B5`, `S-B13` read `ready` with their one precondition absent —
`rawshift` is a pinned submodule that nothing in `Cargo.toml` names, which
the gates table already recorded as "stabilizing, unconsumed". They are
`blocked`.

`S-B14`'s owed column named the wasm entry point and not the larger half:
nothing calls `Lqip::encode`, because producing an `RgbaImage` needs a
decoder and `S-C59` took the only one to `legacy-review/`.

`S-D12` placed the cadence scheduler in core; it is in
`capsule-sdk::recovery`. `S-C59` said the `core-import-media` bucket had
gone; it recorded the decision and left the directory.

Recount: 87 ACTIVE / 75 RETIRED / 43 MIXED, and 98 done / 57 done* / 28
ready / 9 part / 9 blocked / 4 post-v1.
`capsule-sdk/openapi.json` does not exist — `S-C59` deleted it and made
`capsule-server/openapi.json` the one contract — yet eight places still
named it, four of them Rust doc comments a reader would grep from. Where the
citation was about history (`S-C28`'s status audit, the conformance
rationale) it now says so; where it was a live claim it names the file that
exists.

Three further claims the tree disproves:

- `developer-docs.md` said the REST reference was blocked because no Kynos
  document existed. `gen_openapi.rs` emits fifty-nine operations at OpenAPI
  3.2 and `openapi-check-kynos` gates them inside `check-rust`; what is left
  is the reference generation, which is `S-Z9`.
- `filesystem/server.md` said the store in the tree is still flat.
  `capsule-server::blob`'s filesystem backend writes
  `blobs/{hash[0:2]}/{hash[2:4]}/{hash}.bin`, creates each shard on demand
  and fsyncs it.
- `dependencies.md`, `gen_openapi.rs` and ADR-0002 gave the wrong reason for
  the four `spargen::omit!` narrowings. The structurally invalid Salvo
  operations are gone; the four narrowed today are the `application/cbor`
  ones, because spargen's `classify_media` has no such media type.

`mls-resilience.md` put `ReconcileOutcome` in `capsule-core::crypto::mls`,
which has never existed; it is `capsule-core::crypto::authority`, and its
entry point takes a `ServerChainView`. The unbackticked citation inside a
fenced block is exactly what `module-paths` cannot see.

`licensing.md` described the `VideoTranscoder` seam in the present tense
after `S-C59` retired the module holding it; it is a constraint on the
rebuild.

`capsule-cli`'s `DEFAULT_ENDPOINT` comment pointed at `mise run serve-api`,
a task that retired with the binary it launched.

`module-map.md` gains the normative sentence ADR-0005 needs: an app links
exactly the `capsule_core_ffi` + `capsule_sdk` namespace pair.
Each was already being acted on and was written down nowhere a reader could
find it, which is the condition `adr/README.md` exists to end.

ADR-0004 retires `capsule-wire`. It was extracted so the response taxonomy
could outlive the framework, and Kynos removes the defect it was extracted
to fix: the status is part of the return type, there is one declaration, and
`conformance.rs` asserts both directions of the agreement. The server owns
`problem`, `limits` and `body`; the only occurrence of `capsule_wire` outside
the crate is a prose reference in a module comment; a third of the crate
adapts a framework the workspace no longer has. `proposed`, with no
`Contract:` line: the crate is still a workspace member and still a
dependency of `capsule-server`, and #400 lands it.

ADR-0005 records that an app links exactly the `capsule_core_ffi` +
`capsule_sdk` pair. Three namespaces exist and the number reads as three
things an app links; it is not, because two Rust staticlibs cannot share a
binary. `accepted`, and `module-map.md` gains the sentence its `Contract:`
line points at.

ADR-0006 records the server's runtime shape — one binary, one configuration
loader, three adapters per typed port, no generic TTL or CAS abstraction.
`proposed`: `src/bin/` holds only `gen_openapi.rs`, `src/store/` holds only
the in-memory double, and nothing reads a configuration variable. #401-#403
land it.

The filename says "three adapters per port" rather than "three adapter
pairs": `store/mod.rs` declares six typed stores, not two.
The `Owns` cells were written from directory listings and got several
packages wrong in ways a reader would act on. `AssetKit` is the
asset-provider abstraction over PhotoKit and the managed store, not a
windowing layer; `CapsuleCatalog` owns the contract, the Swift-native models
and the in-memory reference implementation, and the `CatalogError` mapping
belongs to `CapsuleCatalogFFI`; `FeatureCollections` is the collections home
rather than three named screens. `capsule-core-swift` and
`capsule-core-kotlin` are standalone harnesses that link `capsule-core` over
uniffi and ship the `HardwareSigner` references, not adapter libraries in
their own right. `capsule-web`'s row now says the thing a reader most needs
from it: it cannot enroll, upload or edit, because a browser holds neither
key.

Each cell now paraphrases the comment in `Project.swift` or the package's own
README, which are the sources that move when the package does.
The oracles read the files this repository happens to have rather than the
files their formats allow, so the promise at the top of `ROADMAP.md` — that
adding a package fails the gate until a row exists — did not hold as written.

- **Package roots one level down were invisible.** `manifestDirs` scanned
  only the repository root, so `apps/viewer/package.json` declared a package
  no gate could see. It now recurses exactly one level, skipping hidden and
  pruned directories, and names a nested root repo-relative — the convention
  `Cargo.toml` already uses for `capsule-cli/entity`. A package root is not
  searched for further roots: a bun or cargo package legitimately carries
  sub-manifests that are not packages. The depth bound is documented as a
  bound; the alternative is walking `node_modules` and `target` on every
  docs-only pull request.
- **Kotlin's `include` is variadic.** `include(":a", ":b")` is one call
  declaring two modules, and the DSL tolerates `include (…)`. The regex
  matched neither.
- **Slice-id resolution covered one column.** `Next milestone` and `Notes`
  cite slices constantly, and so does the deferred register below the table;
  four stale citations in this file were found in exactly those unchecked
  cells. Resolution is now a single pass over every backticked `S-…` in the
  document, reported once, with the `Open slices` column keeping only its
  shape check.
- **`miseTasks` did not know two spellings mise supports.** A quoted header,
  `[tasks."docs:build"]`, and a file task in a subdirectory, where
  `mise-tasks/docs/build` is `docs:build`. Neither is used here yet, and
  missing either would have failed a *correct* row — the worse of the two
  ways for a gate to be wrong.

`checkRoadmap`'s `@returns` now names `unused`, which it has returned since
it was written.

Ten new unit tests; 42 in this file, 107 across `capsule-docs`.
- `capsule-wire` said only retired code depends on it. `capsule-server`
  declares it at `Cargo.toml:49` — ADR-0004 in this same branch says so — and
  what is true is narrower: declared, and called by nothing.
- `capsule-wasm`'s `Owns` claimed LQIP decode. `grep -rni lqip capsule-wasm/`
  is empty, and the row's own `Notes` says `S-B14` owes it the entry point.
- `capsule-android` was `blocked`, which this file defines as a dependency
  *outside* the package gating it. The missing DI layer is inside the package
  and the work has started, so it is `stabilizing` — with the caveat that
  matters spelled out, since `check-kotlin` is lint-only and passes on a
  package that does not compile.
- `capsule-server` has a binary, `gen_openapi`; what it lacks is a *serve*
  binary.
- `capsule-cli/entity` has five entities, not the two named.
- `xtask` does not check licences: `mise run license-check` is `cargo deny`
  over `deny.toml`.

`blocked` now joins `frozen` as a defined state no row uses, which the
`roadmap` check reports rather than failing, for the reason recorded in the
pull request.
- **`prost` left the manifests, not the dependency graph.** `Cargo.lock`
  resolves `prost v0.13.5` through `tzf-rs`, which `capsule-core` takes for
  timezone lookup. `architecture-check` reads *declared* dependencies, so a
  transitive edge is outside what it proves — and `prost` is on its retired
  list, which makes "left the dependency tree" a claim the gate does not
  back. Same overreach in `S-D1`'s note: the crate's *manifest* declares no
  retired dependency.
- **The `ACTIVE` legend still excluded `capsule-core`'s exif tree** while the
  `RETIRED` row two lines down, and the Baseline, both say it is live.
- **`S-B2` still called its EXIF input `RETIRED`.** Its derivative input is;
  `capsule-core::exif` is not.
- **`mls-resilience.md`'s contract skeleton named `MlsError`**, which exists
  nowhere in the workspace. `resilience.rs:206` and `:270` take `&mut self`
  and return `Result<ReconcileOutcome>` and `Result<RekeyOutcome>`.
- **ADR-0004 called `salvo_adapter.rs` a third of the crate.** It is 267 of
  528 lines.

Decision 12: a `MIXED` remainder with a named live home is `done*` with an
`Owed →` pointer, not "owed by construction". `S-D8` was already read that
way, so `S-D1` and `S-D2` gain `Owed → S-Q1` (E2E cases 2 and 3, #409) and
`S-D7` gains `Owed → S-D17` (#408); the rule at the top of the file now says
which of the two readings applies when. Recount: 95 done / 60 done*.

The prose head now points at `ROADMAP.md` and says which file answers which
question.
The cell sat one line above the `RETIRED` cell that #423 rewrites when the
`core-import-media` bucket goes, and it had grown a forward reference into
exactly the text that PR replaces. Deleting `exif` from the list is the whole
of the repair; the cross-reference added a semantic coupling between two
adjacent table rows two different branches are editing, for no gain.
justin13888 added a commit that referenced this pull request Sep 2, 2026
…omment

Two loose ends from the `docs-truth` filter widening.

`hk.pkl`'s `check-docs-truth` step says it "Mirrors the `docs-truth` paths
filter in ci.yml", which stopped being true the moment eight manifest globs
went into ci.yml alone — so the same package-addition that now fires the CI job
still slipped past pre-push. Both lists carry the eight.

The ci.yml comment also described the `roadmap` check in the present tense, but
neither that check nor the ROADMAP.md it reads exists at this commit; both
arrive with `#422`. Say so, and say why the globs are correct to land first.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant