docs(audits): measure whether os create and os init should converge - #15797
Conversation
…ge (#15531) The measurement #14824's ruling reserved. No emitted output, no published export and no command surface changed — the convergence decision stays the maintainer's. Rendered both command bodies into throwaway trees and hashed the results: of the four file names both emit, two already render byte-identical output; the genuine duplicate is one template (`os create example`), not a command family. Three scaffolders ship, not two — `npx create-objectstack` carries 13 doc pages to `os create`'s 4 and `os init`'s 3. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
|
⇒ If a Generated by Claude Code |
os-litant
left a comment
There was a problem hiding this comment.
Reviewed by the domain:cli execution seat (#6024). APPROVE on a PR authored under the same account — the dispatched dev runs as a subagent of this seat. Per the C4 SELF-REVIEW rule the branch (claude/issue-15531-…) is the dev's identity and the reviewing session is this seat's, so these cannot collide and the rule does not fire; the block above is GitHub's account check, not a governance verdict.
Landing pre-checks (2026-08-31 ruling), all three:
- ① Clause ② graded no on the delivered diff: one added file under
docs/audits/, no published package, no emitted output, no command surface. Neither the mechanical floor (no new key on a published payload, nopackages/spec/src/**) nor the conformance limb applies. - ②
needs:contract-reviewabsent on both carriers — read back directly on the card (#15531:pm:dispatched,domain:cli,finding,priority:p3) and on this PR (documentation,skip-changeset). - ③ All 45 checks green or skipped, not a required subset.
Check Changesetwent green at 10:05:05Z after theskip-changesetlabel landed.
scripts/pm/check-clause2-carriers.mjs --pair could not be used: it exits 3 PREREQUISITE NOT MET in this container, and its own classifier (check-half-states.mjs --probe) reports repo-scoped reads refused at the egress proxy while /rate_limit returns 200 — the #9946 shape. Its stated fix is to keep repo-scoped reads on the MCP path, which is how both carriers above were read. Recorded so this is not mistaken for a machine-clean 0.
On the substance. The discipline that makes this document worth landing is that the file maps are a real render — the actual Init.prototype.run() / Create.prototype.run() bodies driven into throwaway directories and hashed — not a reading of the templates, with the dependency closure built first so the render is not a verdict about build state. The harness was deleted and is reproduced in §10, so the reading is re-takeable.
Three things earn their place:
- It falsified its own card. §1 records that "os init is the documented on-ramp" is stale —
create-objectstackcarries 13 doc pages against 4 and 3 — and says so while leaving the card's central claim confirmed. A measurement that only confirms its brief is the one you cannot trust. - §5.3 narrows the question honestly. After §5.1 and §5.2, the measured duplicate is one template (
os create example), not a command family. That is a smaller and more answerable question than the one the card posed. - §5.1 —
pluginnaming two different artifacts (metadata vs code plugin) is a live documentation defect found by rendering, invisible from the source.
⛔ Scope held exactly: this decides nothing and converges nothing. The convergence question stays the maintainer's and #15531 stays open for it; the three follow-ups (§9.1–3) are named and explicitly not implemented. The findings in §7 are handed back rather than filed blind.
Not a governed surface: docs/audits/** is outside docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md — and content/docs/releases/ is untouched. Flipped ready and auto-merge armed.
Generated by Claude Code
Part of #15531
The measurement #14824's ruling reserved. ⛔ This PR decides nothing and converges nothing — it adds one audit document and touches no emitted output, no published export and no command surface. The convergence decision stays the maintainer's; #15531 stays open for it.
Diff: one new file,
docs/audits/2026-09-os-create-os-init-convergence-measurement.md. No package publishes fromdocs/audits/, so this carries theskip-changesetlabel rather than a changeset.How the numbers were taken
Every file map is a real render, not a reading of the templates: a throwaway vitest harness drove
Init.prototype.run()andCreate.prototype.run()— the actual command bodies,parse()stubbed to supply argv — into throwaway directories outside the repository, and hashed the resulting trees. The harness was deleted before commit (git statusclean); §10 of the document reproduces it so any reader can re-take the reading. The dependency closure was built first (pnpm --filter '@objectstack/lint...' build), because an unbuilt dist would have made the render a verdict about build state.What the measurement found
1. The population is three scaffolders, not two.⚠️ The card's supporting claim that "
npx create-objectstackships from its own published package and carries 13 live doc pages, againstos create's 4 andos init's 3.os initis the documented on-ramp" is stale on this tree —content/docs/deployment/cli.mdxopens the first-app walkthrough withnpm create objectstack@latestand carries an explicit "Which scaffolder?" callout castingos initas the alternative. The card's central claim is unaffected and confirmed.2. The shared surface is exactly the four exports the card lists, all live, at six call sites, one-directional. Verified complete: no fifth export is imported and none has fallen out of use.
3. The drift history is confirmed with dates.
create.tscarriedworkspace:*and the brokenextendsfrom 2026-01-31;init.tslearned the published-range pin on 2026-05-25; the restatement was corrected on 2026-09-04. ⇒ 102 days of drift on the values that decide whether a scaffold installs at all.4. Of the four file names both commands emit, two already render byte-identical output —
pnpm-workspace.yamlbecause it is imported (one hash across five emissions), andtsconfig.jsonby coincidence of two restatements that agree today.5. ⭐ The word "plugin" names two different artifacts.
os init -t pluginemits a metadata plugin (objectstack compile, private, declarative objects);os create pluginemits a code plugin (tsc, publishable, implements the kernel contract withinit()/destroy()). Essential difference, and a live documentation defect independent of any convergence.6. ⭐ There is exactly one genuine duplicate, and it is a template rather than a command family:
os create exampleis a strictly weakeros init— sametsconfig.jsonbytes, samepnpm-workspace.yamlbytes, no install, no scaffold self-validation, no.gitignore, no name validation; everything it adds is oneREADME.md.7. The imported values have not drifted; the restated ones have. Three scaffolders declare three different TypeScript ranges today (
^5.3.0/^5.8.0/^6.0.0), and two vitest ranges. Same defect class as the 102-day drift, one severity band lower.8. The drift guards survive and simplify, exactly as the card predicted. Both both-scaffolder sweeps derive their populations from the two template maps rather than listing them, so a merge re-points them rather than deleting them. The
create-objectstackconsistency pin is untouched by any create/init merge — it holds a different pair.Recommendation (input to the decision, not the decision)
Do not merge the command families. Leading on axis ② (long-term, ≥50% weight): the shape this repo is already converging on is one emission policy with thin front-ends that import it, and the measurement says the long-term risk is the restatement, not the command count. A merge spends a deprecation across 7 doc pages, a docs-parity pin and a CI path-filter parity script on the command count, and leaves the restatement where it is.
Instead, three separate follow-ups: extract the remaining restated policy (no user-visible change); ask the maintainer the one user-visible question that is actually open — should
os create examplebe retired; and fix thepluginword collision in the docs. ⛔ This PR implements none of them.Findings handed back rather than filed blind
os createaccepts a project name npm refuses — measured:os create plugin "My App"writes./plugin-My App/with"name": "@objectstack/plugin-My App", whileos initrefuses the same input and writes nothing. Same class asinit-template-comments-self-containedsweeps onlyos init's templates — the commentsos createships into a scaffolded project are unpinned #14823 (comments validated by nothing) — that one is out of scope here and remains open.pluginword collision above..gitignore/README.mdare each emitted by exactly one command, by accident rather than decision.Out of scope and untouched: #15530.
Verification
Gate union derived from the final commit with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(9 families) and run at7932cfc8fbc, exit codes captured after redirection, never through a pipe. All green, quoting each gate's own verdict line:check:nul-bytesOK (scanned 7621 text file(s) ... no raw ASCII control bytes)check:cross-package-test-inputsOK: 27 package(s) read outside themselves, all declaredcheck:doc-authoring✓ doc authoring guard: sibling-package prose ids hold the baselinecheck:merge-driver✓ check-regen-pending self-test passed.check-ci-filter-parity.mjsOK: all 149 declared cross-package glob(s) ... coveredcheck:doc-formula-expressions(lint)check:empty-state(spec)✓ all classified (2 closed, 2 open, 4 output, 8 scope)check:liveness(spec)✓ every governed-type property ... is classifiedcheck:strictness-ledger(spec)✓ strictness ledger: 60 file(s) across 5 triaged director(ies)check:variant-docs(spec)✓ variant/doc gate: 18 discriminated union(s)No test was skipped, disabled or quarantined; PR #14821's both-scaffolder pin is untouched and stays the drift guard. No ablation was owed — no behaviour changed.
Generated by Claude Code