@ybotok/loam 0.0.0 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/COMPARISON.md +63 -0
  3. package/LICENSE +21 -0
  4. package/MIGRATING-from-OpenSpec.md +202 -0
  5. package/README.md +246 -9
  6. package/SCHEMA.md +425 -0
  7. package/SECURITY.md +9 -0
  8. package/dist/cli.js +142 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/commands/adopt.js +240 -0
  11. package/dist/commands/adopt.js.map +1 -0
  12. package/dist/commands/archive.js +1313 -0
  13. package/dist/commands/archive.js.map +1 -0
  14. package/dist/commands/delta.js +388 -0
  15. package/dist/commands/delta.js.map +1 -0
  16. package/dist/commands/dependencies.js +83 -0
  17. package/dist/commands/dependencies.js.map +1 -0
  18. package/dist/commands/docs-repo-gate.js +104 -0
  19. package/dist/commands/docs-repo-gate.js.map +1 -0
  20. package/dist/commands/doctor.js +85 -0
  21. package/dist/commands/doctor.js.map +1 -0
  22. package/dist/commands/explore.js +195 -0
  23. package/dist/commands/explore.js.map +1 -0
  24. package/dist/commands/format.js +17 -0
  25. package/dist/commands/format.js.map +1 -0
  26. package/dist/commands/gherkin.js +309 -0
  27. package/dist/commands/gherkin.js.map +1 -0
  28. package/dist/commands/init.js +250 -0
  29. package/dist/commands/init.js.map +1 -0
  30. package/dist/commands/instructions.js +70 -0
  31. package/dist/commands/instructions.js.map +1 -0
  32. package/dist/commands/list.js +291 -0
  33. package/dist/commands/list.js.map +1 -0
  34. package/dist/commands/migrate-openspec.js +950 -0
  35. package/dist/commands/migrate-openspec.js.map +1 -0
  36. package/dist/commands/new.js +413 -0
  37. package/dist/commands/new.js.map +1 -0
  38. package/dist/commands/rebase.js +356 -0
  39. package/dist/commands/rebase.js.map +1 -0
  40. package/dist/commands/show.js +320 -0
  41. package/dist/commands/show.js.map +1 -0
  42. package/dist/commands/status.js +254 -0
  43. package/dist/commands/status.js.map +1 -0
  44. package/dist/commands/unarchive.js +317 -0
  45. package/dist/commands/unarchive.js.map +1 -0
  46. package/dist/commands/validate.js +1591 -0
  47. package/dist/commands/validate.js.map +1 -0
  48. package/dist/commands/verify.js +775 -0
  49. package/dist/commands/verify.js.map +1 -0
  50. package/dist/commands/vouch.js +343 -0
  51. package/dist/commands/vouch.js.map +1 -0
  52. package/dist/core/agent.js +2065 -0
  53. package/dist/core/agent.js.map +1 -0
  54. package/dist/core/agents-stamp.js +68 -0
  55. package/dist/core/agents-stamp.js.map +1 -0
  56. package/dist/core/arch.js +130 -0
  57. package/dist/core/arch.js.map +1 -0
  58. package/dist/core/brief.js +733 -0
  59. package/dist/core/brief.js.map +1 -0
  60. package/dist/core/coherence.js +577 -0
  61. package/dist/core/coherence.js.map +1 -0
  62. package/dist/core/concurrency.js +51 -0
  63. package/dist/core/concurrency.js.map +1 -0
  64. package/dist/core/config.js +180 -0
  65. package/dist/core/config.js.map +1 -0
  66. package/dist/core/delta.js +476 -0
  67. package/dist/core/delta.js.map +1 -0
  68. package/dist/core/dependencies.js +356 -0
  69. package/dist/core/dependencies.js.map +1 -0
  70. package/dist/core/docs.js +144 -0
  71. package/dist/core/docs.js.map +1 -0
  72. package/dist/core/doctor.js +559 -0
  73. package/dist/core/doctor.js.map +1 -0
  74. package/dist/core/document-bytes.js +59 -0
  75. package/dist/core/document-bytes.js.map +1 -0
  76. package/dist/core/explore.js +225 -0
  77. package/dist/core/explore.js.map +1 -0
  78. package/dist/core/fleet-context.js +225 -0
  79. package/dist/core/fleet-context.js.map +1 -0
  80. package/dist/core/frontmatter.js +199 -0
  81. package/dist/core/frontmatter.js.map +1 -0
  82. package/dist/core/gherkin.js +463 -0
  83. package/dist/core/gherkin.js.map +1 -0
  84. package/dist/core/health.js +67 -0
  85. package/dist/core/health.js.map +1 -0
  86. package/dist/core/ids.js +122 -0
  87. package/dist/core/ids.js.map +1 -0
  88. package/dist/core/issue.js +9 -0
  89. package/dist/core/issue.js.map +1 -0
  90. package/dist/core/json.js +74 -0
  91. package/dist/core/json.js.map +1 -0
  92. package/dist/core/likec4.js +479 -0
  93. package/dist/core/likec4.js.map +1 -0
  94. package/dist/core/maturity.js +66 -0
  95. package/dist/core/maturity.js.map +1 -0
  96. package/dist/core/openapi-merge.js +552 -0
  97. package/dist/core/openapi-merge.js.map +1 -0
  98. package/dist/core/openapi.js +255 -0
  99. package/dist/core/openapi.js.map +1 -0
  100. package/dist/core/openspec-inventory.js +1440 -0
  101. package/dist/core/openspec-inventory.js.map +1 -0
  102. package/dist/core/path-safety.js +168 -0
  103. package/dist/core/path-safety.js.map +1 -0
  104. package/dist/core/provenance.js +720 -0
  105. package/dist/core/provenance.js.map +1 -0
  106. package/dist/core/records.js +33 -0
  107. package/dist/core/records.js.map +1 -0
  108. package/dist/core/repo.js +436 -0
  109. package/dist/core/repo.js.map +1 -0
  110. package/dist/core/report.js +47 -0
  111. package/dist/core/report.js.map +1 -0
  112. package/dist/core/results.js +199 -0
  113. package/dist/core/results.js.map +1 -0
  114. package/dist/core/spec.js +481 -0
  115. package/dist/core/spec.js.map +1 -0
  116. package/dist/core/staging.js +890 -0
  117. package/dist/core/staging.js.map +1 -0
  118. package/dist/core/status.js +1104 -0
  119. package/dist/core/status.js.map +1 -0
  120. package/dist/core/steps.js +72 -0
  121. package/dist/core/steps.js.map +1 -0
  122. package/dist/core/verify.js +807 -0
  123. package/dist/core/verify.js.map +1 -0
  124. package/dist/core/version.js +24 -0
  125. package/dist/core/version.js.map +1 -0
  126. package/package.json +63 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/commands/verify.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam verify <FEAT>` — the done-check.\n *\n * Two modes, one subject. Without `--record`/`--results` it derives the\n * checklist from the feature's own artifacts and reports it, together with\n * whatever has already been answered. With answers it records them, refusing\n * everything that does not correspond to the current checklist — and the\n * answers come from two places that never overlap: `--results\n * <cucumber-report.json>` answers every `scenario.tested` claim mechanically\n * (digest-matched against the report's `@loam-digest-…` tags — only a green\n * run confirms one), `--record <answers.json>` takes an agent's answers for\n * exactly the rest. Either alone is legal only when it covers the whole\n * checklist; the complete-record invariant survives the split.\n *\n * An ARCHIVED feature is the one place the checklist is NOT derived. Archive\n * merged the feature's operations into the living openapi, so re-deriving here\n * computes a smaller claim set, the digest mismatches, and a faithful record\n * reads \"stale\" forever with an untrue diagnosis — archive changed the\n * environment, not the feature. So verify renders verification.yaml verbatim as\n * frozen history, and `--record` refuses: it would answer a checklist nobody\n * can re-derive.\n *\n * It reports; it does not gate. Archive refuses an incoherent feature because\n * loam COMPUTED the incoherence from the documents in front of it. A verdict\n * here is somebody's word about code loam never read, and making it the last\n * obstacle before shipping is how a checklist turns into a formality — the\n * cheapest way past a gate is always to say yes.\n */\nimport type { Command } from \"commander\";\nimport { execFile } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { readFile, stat } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { loadConfig } from \"../core/config.js\";\nimport { emitJson, fail, repoPath, reportNoConfig, type ErrorCode } from \"../core/json.js\";\nimport { resolvePortableFileInside } from \"../core/path-safety.js\";\nimport { today } from \"../core/provenance.js\";\nimport { missingFeatureMessage, resolveFeature } from \"../core/repo.js\";\nimport { contestedDigests, readCucumberReport, runnerAnswers, type ReportScenario } from \"../core/results.js\";\nimport {\n attestedNotice,\n buildVerification,\n buildFederatedVerification,\n checkAnswers,\n featureChecklist,\n readVerificationState,\n tallyAnswers,\n tallyRecord,\n verificationPath,\n verificationVerdict,\n writeVerification,\n type Answer,\n type AnsweredBy,\n type AnsweredClaim,\n type Checklist,\n type Claim,\n type ConsumedReport,\n type DiscardedAnswer,\n type Verification,\n type VerifyNotice,\n} from \"../core/verify.js\";\nimport { plural } from \"./format.js\";\n\ninterface VerifyOptions {\n record?: string;\n results?: string;\n service?: string;\n json?: boolean;\n}\n\n/** A claim plus what has been said about it. `unanswered` is the honest default. */\ninterface ClaimStatus {\n id: string;\n kind: string;\n subject: string;\n claim: string;\n verdict: \"confirmed\" | \"unconfirmed\" | \"unanswered\";\n answered_by?: AnsweredBy;\n evidence: string[];\n note?: string;\n}\n\nexport function registerVerify(program: Command): void {\n program\n .command(\"verify\")\n .argument(\"<featureId>\", \"feature id, e.g. FEAT-101\")\n .description(\"Check a shipped feature against its own promises: derive the claims, record the answers\")\n .option(\"--record <file>\", \"record a JSON answer set against the current checklist\")\n .option(\n \"--results <file>\",\n \"answer the scenario.tested claims mechanically from a cucumber JSON test report\",\n )\n .option(\n \"--service <id>\",\n \"record only this service's claims, bound to the current repository commit\",\n )\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (featureId: string, opts: VerifyOptions) => {\n const json = opts.json === true;\n\n const config = await loadConfig();\n if (!config) {\n reportNoConfig(json);\n return;\n }\n const { docsDir } = config;\n const recording = opts.record !== undefined || opts.results !== undefined;\n\n // WRITING is bound to the repository; READING is not. An attestation\n // pins claims to this repo's git HEAD and to file:line evidence inside\n // it, so `--record --service X` may only run where loam.json says this\n // repo IS X — vouch's and gherkin's refusal, for vouch's reason: from\n // anywhere else, that repository is somebody else's. Reading takes\n // `--service` as a pure lens (which claims are checkout-web's, and what\n // has it said?) and needs no binding at all, because it writes nothing.\n if (recording && opts.service !== undefined) {\n if (config.service === undefined) {\n return fail(\n json,\n \"repository-unavailable\",\n `Cannot attest for '${opts.service}' from here: this is not a service repo — loam.json declares no \\`service\\`. ` +\n `A federated attestation binds the answers to this repository's git HEAD and to evidence inside it, so the repository has to say which service it is. ` +\n `Add \"service\": \"${opts.service}\" to loam.json, or record it from that service's own repo.`,\n );\n }\n if (config.service !== opts.service) {\n return fail(\n json,\n \"service-mismatch\",\n `This repository is configured as service '${config.service}', so it cannot attest claims for '${opts.service}'.`,\n );\n }\n }\n\n // Archived features resolve too: a shipped feature's verification is worth\n // reading back, and it travelled into the archive with everything else.\n const feature = await resolveFeature(docsDir, featureId, \"include\");\n if (!feature) {\n // repo.ts's sentence, not a second copy of it. Resolving with \"include\"\n // means the archived branch inside it cannot fire here — an archived\n // feature resolves — so the text is exactly what it was; what changes is\n // that a reworded miss message can no longer leave this one behind.\n return fail(json, \"unknown-target\", await missingFeatureMessage(docsDir, featureId));\n }\n\n // Before anything else: a verification.yaml that exists but will not read\n // is a refusal, not an absence. It holds somebody's answers and — in a\n // fleet — other repositories' attestations; treating it as \"not verified\"\n // let the next --record silently overwrite all of it, and made the read\n // view say \"no record\" about a file sitting right there.\n const existing = await readVerificationState(feature.dir);\n if (existing.state === \"unreadable\") {\n return fail(\n json,\n \"record-unreadable\",\n `${repoPath(docsDir, verificationPath(feature.dir))} exists but cannot be read as a verification record: ${existing.reason}. ` +\n (existing.code === \"verify.record-miscounted\"\n ? \"Record the answers again rather than editing the counts: a summary made to agree by hand still says nothing about what was checked.\"\n : \"It is plain YAML — repair it by hand, or delete it and record again. loam will not overwrite a record it could not read.\"),\n );\n }\n const recorded = existing.state === \"ok\" ? existing.verification : null;\n\n // But an archived feature never gets a re-derived checklist — see the\n // header. Its record is frozen history, and --record / --results refuse\n // alike: the code is `invalid-option` rather than `answers-mismatch`\n // because the answers were never compared to anything — there is no\n // current checklist to answer, so the wrong thing here is the option,\n // not the answer set.\n if (feature.archived) {\n if (recording) {\n return fail(\n json,\n \"invalid-option\",\n `${feature.id} is archived — its verification is history now. \\`loam unarchive ${feature.id}\\` first if the answers really need to change.`,\n );\n }\n reportFrozen(docsDir, feature.dir, feature.id, recorded, json);\n return;\n }\n\n const checklist = await featureChecklist(docsDir, feature.dir, feature.id);\n\n if (recording) {\n // Standing in a service repo, `--service` is what the repo already\n // says: omitting it must not fall back to the legacy all-at-once form,\n // which claims the whole fleet's checklist on this one repo's word.\n //\n // The repo root, not the cwd — `vouch`'s and `gherkin`'s reason. An\n // attestation names a commit and rests on evidence resolved inside\n // \"this repository\", and `config.root` is the directory loam.json was\n // actually found in. Off `process.cwd()`, running from a subdirectory\n // silently changed all three: evidence resolved against the\n // subdirectory, `--results` had to live in it, and `git rev-parse` ran\n // there — a submodule or nested checkout under it would have named\n // somebody else's commit on this repository's attestation.\n await record(\n docsDir,\n feature.dir,\n checklist,\n opts.service ?? config.service,\n config.root ?? process.cwd(),\n recorded,\n opts,\n json,\n );\n return;\n }\n\n report(docsDir, feature.dir, checklist, recorded, json, {\n lens: opts.service,\n bound: config.service,\n });\n });\n}\n\n/* ------------------------------------------------------------------ */\n/* Reading */\n/* ------------------------------------------------------------------ */\n\nfunction statuses(checklist: Checklist, claims: Checklist[\"claims\"], recorded: Verification | null): ClaimStatus[] {\n const subjects = new Map(checklist.claims.map((claim) => [claim.id, claim.subject]));\n const attested =\n recorded?.schema !== 2\n ? null\n : new Set(\n (recorded.attestations ?? []).flatMap((attestation) =>\n attestation.claims.filter((id) => subjects.get(id) === attestation.service),\n ),\n );\n const byId = new Map(\n (recorded?.claims ?? [])\n .filter((claim) => attested === null || attested.has(claim.id))\n .map((claim) => [claim.id, claim]),\n );\n return claims.map((c) => {\n const answer = byId.get(c.id);\n return {\n id: c.id,\n kind: c.kind,\n subject: c.subject,\n claim: c.claim,\n verdict: answer?.verdict ?? \"unanswered\",\n ...(answer?.answered_by === undefined ? {} : { answered_by: answer.answered_by }),\n evidence: answer?.evidence ?? [],\n ...(answer?.note === undefined ? {} : { note: answer.note }),\n };\n });\n}\n\n/**\n * The read view.\n *\n * `lens` is `--service`: the same checklist, narrowed to the claims that\n * service's code answers. It exists because in a ten-repo fleet the whole\n * checklist is never one repository's business, and an agent that cannot ask\n * \"what do I still owe on FEAT-1?\" answers the question by guessing. Narrowing\n * narrows the verdict too — `verified` then means \"every claim owned by this\n * service is confirmed\", which is why the payload names the service it applied.\n *\n * `bound` is loam.json's `service` — what this repository says it is. It only\n * shapes the footer, and it is the whole of how federation is discoverable:\n * the recording form printed here is the one that will actually work from here.\n */\nfunction report(\n docsDir: string,\n featureDir: string,\n checklist: Checklist,\n recorded: Verification | null,\n json: boolean,\n scope: { lens?: string; bound?: string } = {},\n): void {\n const lens = scope.lens;\n const owners = [...new Set(checklist.claims.map((c) => c.subject))].sort();\n const scoped = lens === undefined ? checklist.claims : checklist.claims.filter((c) => c.subject === lens);\n const claims = statuses(checklist, scoped, recorded);\n const count = (v: string): number => claims.filter((c) => c.verdict === v).length;\n const summary = {\n claims: claims.length,\n confirmed: count(\"confirmed\"),\n unconfirmed: count(\"unconfirmed\"),\n unanswered: count(\"unanswered\"),\n };\n // A record that answers a different question set is not an answer to this one,\n // however complete it looks — so staleness disqualifies it as a whole.\n const stale = recorded !== null && recorded.checklist !== checklist.digest;\n const tally = tallyAnswers(claims);\n const verdict = verificationVerdict(tally, stale);\n // `verified` keeps its name and its type, and loses its old meaning: a\n // scenario claim on an agent's word is answered, not run, so it no longer\n // reads the same as a green suite. `verdict` carries the distinction.\n const verified = verdict === \"verified\";\n // Contest is derivable from the checklist alone, so the read view says it\n // too: an agent looking at what it owes should learn that a second service\n // answers the same claim BEFORE it runs a suite whose report cannot be\n // attributed. Always from the whole checklist, never the lens — a shared\n // digest is a fact about the feature, and a narrowed view is the one place it\n // would otherwise be invisible.\n const notices = [...noticesFor(claims, checklist.feature), ...contestedNotices(checklist.claims)];\n\n if (json) {\n emitJson({\n feature: checklist.feature,\n path: repoPath(docsDir, featureDir),\n digest: checklist.digest,\n // Named only when narrowed, so a consumer can never mistake a service's\n // verdict for the feature's.\n ...(lens === undefined ? {} : { service: lens, checklistClaims: checklist.claims.length }),\n verified,\n verdict,\n attested: tally.attested,\n summary,\n services: owners,\n recorded:\n recorded === null\n ? null\n : {\n path: repoPath(docsDir, verificationPath(featureDir)),\n recorded: recorded.recorded,\n checklist: recorded.checklist,\n stale,\n ...(recorded.attestations === undefined ? {} : { attestations: recorded.attestations }),\n ...(recorded.report === undefined ? {} : { report: recorded.report }),\n },\n claims,\n ...(notices.length === 0 ? {} : { notices }),\n });\n return;\n }\n\n const scopeNote =\n lens === undefined ? \"\" : ` owned by ${lens} (of ${plural(checklist.claims.length, \"claim\")} on the checklist)`;\n console.log(\n `${checklist.feature} — ${plural(claims.length, \"claim\")}${scopeNote} derived from ${repoPath(docsDir, featureDir)}\\n`,\n );\n if (claims.length === 0) {\n console.log(\n lens === undefined\n ? \" Nothing to check: this feature's delta, specs and openapi promise nothing yet.\"\n : ` No claim on this checklist is owned by '${lens}'.${owners.length > 0 ? ` The services that own claims here are ${owners.join(\", \")}.` : \"\"}`,\n );\n return;\n }\n for (const c of claims) {\n console.log(` ${MARK[c.verdict]} ${c.id} [${c.subject}] ${c.claim}${answeredMark(c)}`);\n for (const e of c.evidence) console.log(` ${e}`);\n if (c.note !== undefined) console.log(` note: ${c.note}`);\n }\n\n console.log(\"\");\n if (recorded === null) {\n console.log(` Not verified — no ${repoPath(docsDir, verificationPath(featureDir))}.`);\n } else {\n console.log(\n ` Recorded ${recorded.recorded} — ${summary.confirmed} confirmed, ${summary.unconfirmed} unconfirmed, ${summary.unanswered} unanswered.`,\n );\n if (stale) {\n console.log(\" STALE: the feature changed after this was recorded. Answer the claims above again.\");\n }\n for (const attestation of recorded.attestations ?? []) {\n console.log(\n ` Attested by ${attestation.service} at ${attestation.commit.slice(0, 12)} (${attestation.recorded}, ${plural(attestation.claims.length, \"claim\")}).`,\n );\n if (attestation.report !== undefined) console.log(` from ${reportLine(attestation.report)}`);\n }\n if (recorded.report !== undefined) console.log(` Answered from ${reportLine(recorded.report)}.`);\n }\n for (const notice of notices) console.log(` ⚠ ${notice.code}: ${notice.message}`);\n // An attested record has answered every question — telling it to \"answer each\n // claim\" would send an agent round a loop it has already run. The notice\n // above names the one thing left, which is a test run.\n if (verdict === \"unverified\") {\n console.log(\"\\n Answer each claim, then record the answers:\\n\");\n console.log(' [{ \"id\": \"<claim id>\", \"verdict\": \"confirmed\", \"evidence\": [\"src/x.ts:42\"] }]');\n console.log(`\\n loam verify ${checklist.feature} --record answers.json${recordSuffix(scope, owners)}`);\n for (const line of federationNote(scope, owners)) console.log(line);\n console.log(\"\\n A claim you cannot show evidence for is `unconfirmed` — say why in `note`.\");\n }\n}\n\n/**\n * The `--service` the printed form should carry. It comes from loam.json, never\n * from the checklist: the only attestation this repository can make is its own,\n * and a hint naming a service that lives somewhere else is a hint that fails.\n */\nfunction recordSuffix(scope: { lens?: string; bound?: string }, owners: string[]): string {\n if (scope.bound !== undefined) return ` --service ${scope.bound}`;\n return owners.length > 1 || scope.lens !== undefined ? \" --service <svc>\" : \"\";\n}\n\n/** Why the form above has a `--service` on it — the one place federation is taught. */\nfunction federationNote(scope: { lens?: string; bound?: string }, owners: string[]): string[] {\n if (scope.bound !== undefined) {\n const others = owners.filter((s) => s !== scope.bound);\n return [\n \"\",\n ` This repository is ${scope.bound}, so it attests only ${scope.bound}'s claims, bound to its git HEAD.`,\n ...(others.length === 0\n ? []\n : [` The claims owned by ${others.join(\", \")} are recorded the same way from those repositories.`]),\n ];\n }\n if (owners.length > 1 || scope.lens !== undefined) {\n return [\n \"\",\n ` This checklist spans ${plural(owners.length, \"service\")} (${owners.join(\", \")}) — each attests its own claims`,\n \" from its own repository, where loam.json names that service.\",\n ];\n }\n return [];\n}\n\n/**\n * The frozen view: an archived feature's record, verbatim. No checklist is\n * derived and no staleness is judged — there is nothing current to judge\n * against, and pretending otherwise is how a true record reads as a lie.\n * `frozen` is the marker a consumer branches on.\n *\n * The verdict is recounted from `claims[]`, never taken from `summary`: this is\n * the post-ship verdict on a feature nobody can re-derive a checklist for, so a\n * record whose counts had drifted from its own answers reported a shipped\n * feature as fully confirmed. (A record that contradicts itself never reaches\n * here — `readVerificationState` refuses it — so the recount and the summary\n * agree; the recount is what says WHY.) No record at all is reported as exactly\n * that, with `summary: null`, not zero claims, which would falsely say the\n * feature promised nothing.\n */\nfunction reportFrozen(\n docsDir: string,\n featureDir: string,\n featureId: string,\n v: Verification | null,\n json: boolean,\n): void {\n const tally = v === null ? null : tallyRecord(v);\n const verdict = tally === null ? \"unverified\" : verificationVerdict(tally);\n const notices = v === null ? [] : noticesFor(v.claims, featureId);\n\n if (json) {\n emitJson({\n feature: featureId,\n path: repoPath(docsDir, featureDir),\n frozen: true,\n verified: verdict === \"verified\",\n verdict,\n attested: tally?.attested ?? 0,\n summary: v === null ? null : v.summary,\n recorded:\n v === null\n ? null\n : {\n path: repoPath(docsDir, verificationPath(featureDir)),\n recorded: v.recorded,\n checklist: v.checklist,\n ...(v.attestations === undefined ? {} : { attestations: v.attestations }),\n ...(v.report === undefined ? {} : { report: v.report }),\n },\n claims: v === null ? [] : v.claims,\n ...(notices.length === 0 ? {} : { notices }),\n });\n return;\n }\n\n if (v === null) {\n console.log(\n `${featureId} is archived and has no verification record — nothing was recorded before it shipped.`,\n );\n return;\n }\n\n console.log(\n `${featureId} — verification recorded ${v.recorded}, frozen at archive (${repoPath(docsDir, featureDir)})\\n`,\n );\n for (const c of v.claims) {\n // `subject` is absent in records written before schema 2 — an old record\n // must read as itself, not gain a service it never named.\n const subject = c.subject === undefined ? \"\" : ` [${c.subject}]`;\n console.log(` ${MARK[c.verdict]} ${c.id}${subject} ${c.claim}${answeredMark(c)}`);\n for (const e of c.evidence) console.log(` ${e}`);\n if (c.note !== undefined) console.log(` note: ${c.note}`);\n }\n const counts = tallyRecord(v);\n console.log(`\\n Recorded ${v.recorded} — ${counts.confirmed} confirmed, ${counts.unconfirmed} unconfirmed.`);\n console.log(\n \" This checklist is frozen at record time: the feature is archived and its claims are not re-derived.\",\n );\n for (const attestation of v.attestations ?? []) {\n console.log(\n ` Attested by ${attestation.service} at ${attestation.commit.slice(0, 12)} (${attestation.recorded}).`,\n );\n if (attestation.report !== undefined) console.log(` from ${reportLine(attestation.report)}`);\n }\n if (v.report !== undefined) console.log(` Answered from ${reportLine(v.report)}.`);\n for (const notice of notices) console.log(` ⚠ ${notice.code}: ${notice.message}`);\n}\n\nconst MARK: Record<string, string> = { confirmed: \"✓\", unconfirmed: \"✗\", unanswered: \"?\" };\n\n/**\n * Who answered, where that changes what the verdict means. `[runner]` is a\n * digest-matched green run; `[attested]` is a scenario claim confirmed on\n * somebody's word — the same ✓, a weaker fact, and it must be visible on the\n * line itself and not only in the summary. Everything else is unmarked: an\n * agent's answer about a service existing or an operation being exposed is\n * exactly what the checklist asks for.\n */\nfunction answeredMark(c: AnsweredClaim): string {\n if (c.answered_by === \"runner\") return \" [runner]\";\n return c.kind === \"scenario.tested\" && c.verdict === \"confirmed\" ? \" [attested]\" : \"\";\n}\n\n/** Everything a verify surface has to say about a set of answers beyond the counts. */\nfunction noticesFor(claims: readonly AnsweredClaim[], feature: string): VerifyNotice[] {\n const attested = attestedNotice(claims, feature);\n return attested === null ? [] : [attested];\n}\n\n/**\n * The claims one report could not attribute: a scenario digest — and therefore\n * an `@loam-digest-…` tag — that more than one service claims.\n *\n * This is now a GUARD rather than a diagnosis of ordinary authoring. Until the\n * digest was salted by service, two services wording a scenario identically\n * really did share one tag, and this is what stopped one repository's green run\n * from confirming the other's claim. `scenarioBodyHash` folds the owning\n * service into the hash, so a claim's digest is computed from the same service\n * the claim is filed under and two subjects cannot share one short of a\n * truncated-sha256 collision. It stays because the invariant is worth asserting\n * where it is relied on: if this ever fires, a digest is answering for two\n * services and no report can say which, so nothing here may confirm.\n *\n * Derived from claims rather than from a report, so the read view raises it too\n * — the collision exists the moment the checklist does, and learning about it\n * from a `--results` run that already happened is learning too late.\n */\nfunction contestedNotices(claims: readonly Claim[]): VerifyNotice[] {\n const contested = contestedDigests(claims);\n if (contested.size === 0) return [];\n const shared = [...contested].map(([digest, services]) => `${digest} (${services.join(\", \")})`);\n return [\n {\n code: \"verify.digest-contested\",\n severity: \"warn\",\n message:\n `${plural(contested.size, \"scenario digest\")} claimed by more than one service: ${shared.join(\"; \")}. ` +\n \"The digest is salted by service, so this is a hash collision, not shared wording — but one report still \" +\n \"cannot say whose suite ran them, so a shared --results run leaves them unconfirmed. \" +\n \"Record each service's claims from its own repository with --service.\",\n claims: claims.filter((c) => c.digest !== undefined && contested.has(c.digest)).map((c) => c.id),\n },\n ];\n}\n\n/** The report a `--results` run consumed, as one line a reviewer can check by hand. */\nfunction reportLine(r: ConsumedReport): string {\n return `${r.path} (sha256 ${r.digest.slice(0, 12)}…, ${plural(r.scenarios, \"tagged scenario\")}, written ${r.mtime})`;\n}\n\n/* ------------------------------------------------------------------ */\n/* Recording */\n/* ------------------------------------------------------------------ */\n\n/**\n * `repoDir` is the repository an attestation is ABOUT: the commit it names, the\n * tree its evidence must resolve inside, and where a `--results` report has to\n * live. It is threaded rather than read here because it comes from\n * `config.root`, and a `process.cwd()` in this function was the whole bug —\n * from a subdirectory, \"this repo\" quietly became that subdirectory.\n */\nasync function record(\n docsDir: string,\n featureDir: string,\n checklist: Checklist,\n service: string | undefined,\n repoDir: string,\n previous: Verification | null,\n opts: VerifyOptions,\n json: boolean,\n): Promise<void> {\n // The legacy all-at-once form answers the WHOLE checklist on one repository's\n // word and writes a schema-1 record — no attestations, no commits. Run over a\n // federated record it does not merge and it does not migrate: it erases every\n // other service's commit-bound attestation, and the erasure is invisible\n // afterwards because what replaces it is a well-formed, plausible record. So\n // it refuses, and names the repositories whose word is on the file.\n const attested = previous?.schema === 2 ? (previous.attestations ?? []) : [];\n if (service === undefined && attested.length > 0) {\n const services = [...new Set(attested.map((a) => a.service))].sort();\n return fail(\n json,\n \"record-federated\",\n `${repoPath(docsDir, verificationPath(featureDir))} is a federated record: ${plural(services.length, \"service\")} (${services.join(\", \")}) have attested claims against their own git commits. ` +\n \"Recording without --service would replace all of it with one unattested answer set. \" +\n `Record this service's claims from its own repo instead: \\`loam verify ${checklist.feature} --record answers.json --service <svc>\\`.`,\n );\n }\n\n const scopedClaims =\n service === undefined ? checklist.claims : checklist.claims.filter((claim) => claim.subject === service);\n if (service !== undefined && scopedClaims.length === 0) {\n return fail(\n json,\n \"unknown-service\",\n `The current ${checklist.feature} checklist has no claims owned by service '${service}'.`,\n );\n }\n\n // The runner's half: with --results, every scenario.tested claim is the\n // report's to answer — matched by digest, confirmed only by a green run.\n const runnerClaims =\n opts.results === undefined ? [] : scopedClaims.filter((c) => c.kind === \"scenario.tested\");\n const agentClaims =\n opts.results === undefined ? scopedClaims : scopedClaims.filter((c) => c.kind !== \"scenario.tested\");\n\n let fromRunner: Answer[] = [];\n let consumed: ConsumedReport | undefined;\n if (opts.results !== undefined) {\n // A federated record attests for THIS repository, so its report has to be a\n // file inside it — see readResults.\n const read = await readResults(opts.results, service === undefined ? undefined : repoDir);\n if (!read.ok) return fail(json, read.code, read.message);\n consumed = read.report;\n fromRunner = runnerAnswers(runnerClaims, read.scenarios, consumed.path);\n }\n\n // The agent's half — exactly what the runner does not own. --results alone\n // is legal only when the runner owns the whole checklist: anything left over\n // refuses with the ids, the same discipline as a claim with no answer.\n let raw: unknown = [];\n if (opts.record !== undefined) {\n try {\n // The one path that stays cwd-relative, and deliberately: the answer set\n // is a file the caller just wrote and hands to the command, not part of\n // the repository being attested. Nothing on the record points at it.\n raw = JSON.parse(await readFile(resolve(process.cwd(), opts.record), \"utf8\"));\n } catch (err) {\n return fail(\n json,\n \"answers-unreadable\",\n `Cannot read ${opts.record}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n } else if (agentClaims.length > 0 && opts.results !== undefined) {\n return fail(\n json,\n \"answers-mismatch\",\n `--results answers only the scenario.tested claims; ${agentClaims.length} claim(s) have no answer: ` +\n `${agentClaims.map((c) => c.id).join(\", \")}. Record them with --record <answers.json> alongside --results.`,\n );\n }\n\n const checked = checkAnswers(\n agentClaims,\n raw,\n opts.results === undefined ? undefined : new Set(runnerClaims.map((c) => c.id)),\n { feature: checklist.feature, ...(service === undefined ? {} : { service }) },\n );\n if (!checked.ok) return fail(json, checked.code, checked.message);\n\n let serviceCommit: string | undefined;\n if (service !== undefined) {\n const commit = await repositoryCommit(repoDir);\n if (!commit.ok) return fail(json, \"repository-unavailable\", commit.message);\n serviceCommit = commit.commit;\n // Both halves, one validator. The runner's evidence used to skip it\n // entirely: `--results` minted confirmations whose evidence strings nothing\n // ever checked, in the one mode that promises every answer is bound to this\n // repository at this commit.\n const evidenceFailure = await validateServiceEvidence(\n [...fromRunner, ...checked.answers],\n repoDir,\n serviceCommit,\n consumed,\n );\n if (evidenceFailure !== null) return fail(json, \"answers-unevidenced\", evidenceFailure);\n }\n\n const recorded = today(new Date());\n let verification: Verification;\n let discarded: DiscardedAnswer[] = [];\n if (service === undefined) {\n verification = buildVerification(checklist, [...fromRunner, ...checked.answers], recorded, consumed);\n } else {\n const built = buildFederatedVerification(\n checklist,\n service,\n [...fromRunner, ...checked.answers],\n previous,\n recorded,\n serviceCommit!,\n consumed,\n );\n verification = built.verification;\n discarded = built.discarded;\n }\n const path = await writeVerification(featureDir, verification);\n const unconfirmed = verification.claims.filter((c) => c.verdict === \"unconfirmed\");\n\n // The same judgment read mode makes (see report): the record just written IS\n // the current checklist's answer, so `stale` is false by construction and the\n // remaining question is whether every claim was confirmed — and, now, by\n // whom. Counted from the claims, not from the summary this same call wrote.\n // Without these fields an agent that recorded all-confirmed answers would\n // have to re-run verify just to learn the state it created.\n const tally = tallyRecord(verification);\n const verdict = verificationVerdict(tally);\n const verified = verdict === \"verified\";\n const notices = [...noticesFor(verification.claims, verification.feature), ...contestedNotices(runnerClaims)];\n\n if (json) {\n emitJson({\n feature: verification.feature,\n path: repoPath(docsDir, path),\n digest: verification.checklist,\n verified,\n verdict,\n attested: tally.attested,\n recorded: verification.recorded,\n summary: verification.summary,\n ...(verification.attestations === undefined ? {} : { attestations: verification.attestations }),\n ...(consumed === undefined ? {} : { report: consumed }),\n unconfirmed: unconfirmed.map((c) => ({ id: c.id, claim: c.claim, ...(c.note === undefined ? {} : { note: c.note }) })),\n ...(discarded.length === 0 ? {} : { discarded }),\n ...(notices.length === 0 ? {} : { notices }),\n });\n return;\n }\n\n console.log(`${verification.feature} verification recorded — ${repoPath(docsDir, path)}\\n`);\n console.log(` ${tally.confirmed} of ${plural(tally.claims, \"claim\")} confirmed with evidence.`);\n if (consumed !== undefined) {\n console.log(\n ` ${plural(fromRunner.length, \"scenario claim\")} answered by the test runner (${opts.results})` +\n `${opts.record === undefined ? \"\" : `, ${checked.answers.length} by ${opts.record}`}.`,\n );\n console.log(` Report read: ${reportLine(consumed)}.`);\n }\n if (service !== undefined) {\n const attestation = verification.attestations?.find((item) => item.service === service);\n if (attestation !== undefined) {\n console.log(` ${service} attested at git commit ${attestation.commit}.`);\n }\n }\n for (const c of unconfirmed) {\n console.log(` ✗ ${c.claim}${c.note === undefined ? \"\" : ` — ${c.note}`}`);\n }\n\n // What the previous record answered and this one does not. A federated write\n // is a partial write, so the first one over an all-at-once record drops every\n // answer it cannot attribute to a commit — correct, but silence here reads as\n // loam having lost the answers, and nobody goes looking for what to re-record.\n if (discarded.length > 0) {\n const off = discarded.filter((d) => d.reason === \"off-checklist\").length;\n console.log(\n `\\n ${plural(discarded.length, \"earlier answer\")} from ${repoPath(docsDir, verificationPath(featureDir))} ${discarded.length === 1 ? \"is\" : \"are\"} not carried into this record:`,\n );\n for (const d of discarded) {\n console.log(\n ` - ${d.id}${d.subject === undefined ? \"\" : ` [${d.subject}]`} ${d.claim}` +\n (d.reason === \"off-checklist\"\n ? \" (the feature changed; nothing asks this any more)\"\n : \" (no commit attestation binds it — its service must record it again)\"),\n );\n }\n if (discarded.length > off) {\n console.log(\n ` Each owning service records its own with \\`loam verify ${verification.feature} --record answers.json --service <svc>\\` in its repo.`,\n );\n }\n }\n console.log(\"\");\n for (const notice of notices) console.log(` ⚠ ${notice.code}: ${notice.message}`);\n console.log(\n tally.unanswered > 0\n ? ` Partial federation — ${plural(tally.unanswered, \"claim\")} remain unanswered for their owning service repositories.`\n : verdict === \"attested\"\n ? \" Recorded as attested, not verified: the record travels into features/archive/ saying so.\"\n : unconfirmed.length === 0\n ? \" The record travels with the feature into features/archive/.\"\n : \" Recorded as it stands. Nothing gates on this — it is what a reviewer reads later, so leave it true.\",\n );\n}\n\ntype ResultsRead =\n | { ok: true; report: ConsumedReport; scenarios: ReportScenario[] }\n | { ok: false; code: ErrorCode; message: string };\n\n/**\n * The report as an artifact, not merely as a parse.\n *\n * `repoDir` is set in federated mode, and there the report must be a file\n * INSIDE the repository being attested, resolved by the same rules as evidence\n * (`resolvePortableFileInside`): an attestation says \"at this commit, in this\n * repository\", and a report living somewhere else answers for a run nobody\n * standing here can find. The legacy all-at-once form binds to no repository at\n * all, so it takes the path as spelled — its looser contract, unchanged.\n *\n * Either way the bytes are digested and the file's mtime read, because loam\n * cannot prove a JSON file came from executing this commit and should stop\n * implying otherwise. It can say precisely which file it consumed, and that\n * goes on the record.\n */\nasync function readResults(spelled: string, repoDir: string | undefined): Promise<ResultsRead> {\n let path: string;\n if (repoDir === undefined) {\n path = resolve(process.cwd(), spelled);\n } else {\n try {\n path = resolvePortableFileInside(repoDir, spelled, \"test report\");\n } catch (err) {\n return {\n ok: false,\n code: \"answers-unreadable\",\n message:\n `Cannot answer from ${spelled}: ${err instanceof Error ? err.message : String(err)}. ` +\n \"A federated attestation rests on a report inside the repository it attests — give the path relative to the repo root.\",\n };\n }\n }\n\n let bytes: Buffer;\n let mtime: Date;\n try {\n bytes = await readFile(path);\n mtime = (await stat(path)).mtime;\n } catch (err) {\n return {\n ok: false,\n code: \"answers-unreadable\",\n message: `Cannot read ${spelled} as a cucumber JSON report: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n let doc: unknown;\n try {\n doc = JSON.parse(bytes.toString(\"utf8\"));\n } catch (err) {\n return {\n ok: false,\n code: \"answers-unreadable\",\n message: `Cannot read ${spelled} as a cucumber JSON report: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n const parsed = readCucumberReport(doc, spelled);\n if (!parsed.ok) return { ok: false, code: \"answers-unreadable\", message: parsed.message };\n return {\n ok: true,\n scenarios: parsed.scenarios,\n report: {\n path: spelled,\n digest: createHash(\"sha256\").update(bytes).digest(\"hex\"),\n mtime: mtime.toISOString(),\n scenarios: parsed.scenarios.length,\n },\n };\n}\n\n/**\n * In federated mode, a confirmation is accepted only when its evidence holds up\n * in this repository: an agent's `file:line` resolves to a real line that is\n * unchanged at the attested commit, and the runner's evidence names the report\n * loam just read. Legacy global mode deliberately keeps its original, looser\n * evidence contract for backward compatibility.\n */\nasync function validateServiceEvidence(\n answers: Answer[],\n repoDir: string,\n commit: string,\n report?: ConsumedReport,\n): Promise<string | null> {\n if (report !== undefined) {\n // Most reports are build output and untracked, and `git diff` is quiet on\n // those — nothing here invents a rule for them. One the repository DOES\n // carry has to match the commit being attested, like any other file the\n // evidence rests on.\n const clean = await git(repoDir, [\"diff\", \"--quiet\", commit, \"--\", report.path]);\n if (clean.code !== 0) {\n // Only `1` is git ANSWERING — \"that file differs\". Every other non-zero\n // code is git failing to answer at all: no git on PATH, a fork that never\n // happened, a child killed by a CI timeout or the OOM killer. Testing for\n // `1` alone made those silently pass the check, so the one run that most\n // needs the binding — the one where the machine is falling over — is the\n // one that would mint an attestation whose report was never bound to the\n // commit. An unanswered check is not a passed one, so the unknown refuses\n // on the same terms as the known, and `clean.stderr` carries whatever\n // account of itself the child managed to leave.\n return clean.code === 1\n ? `The test report '${report.path}' is committed to this repository and differs from ${commit.slice(0, 12)} — commit it or attest the commit it belongs to.`\n : `Cannot tell whether the test report '${report.path}' is bound to ${commit.slice(0, 12)} — git could not be run to completion: ${clean.stderr || \"git diff failed\"}`;\n }\n }\n for (const answer of answers) {\n if (answer.verdict !== \"confirmed\") continue;\n if (answer.answered_by === \"runner\") {\n // The runner's evidence is a scenario inside the report, not a file:line\n // in the source — there is nothing to resolve. What must hold is that it\n // names THAT report: an answer pointing anywhere else is not this run's.\n const stray = answer.evidence.filter((e) => report === undefined || !e.startsWith(`${report.path}: `));\n if (stray.length > 0) {\n return `Claim ${answer.id} has runner evidence ${stray.map((e) => `'${e}'`).join(\", \")} that does not name the report loam read.`;\n }\n continue;\n }\n for (const evidence of answer.evidence) {\n const match = /^(.+):([1-9]\\d*)$/.exec(evidence);\n if (match === null) {\n return `Claim ${answer.id} has evidence '${evidence}' — service evidence must be a canonical relative file:line.`;\n }\n const relativePath = match[1]!;\n const line = Number(match[2]);\n let absolutePath: string;\n try {\n absolutePath = resolvePortableFileInside(repoDir, relativePath, `evidence for ${answer.id}`);\n } catch (err) {\n return `Claim ${answer.id} has unsafe evidence '${evidence}': ${err instanceof Error ? err.message : String(err)}`;\n }\n try {\n const info = await stat(absolutePath);\n if (!info.isFile()) {\n return `Claim ${answer.id} has evidence '${evidence}', but '${relativePath}' is not a regular file.`;\n }\n const source = await readFile(absolutePath, \"utf8\");\n const lines = source.split(/\\r\\n|\\n|\\r/).length;\n if (line > lines) {\n return `Claim ${answer.id} has evidence '${evidence}', but '${relativePath}' has only ${lines} line(s).`;\n }\n const committed = await committedFile(repoDir, commit, relativePath);\n if (!committed.ok) {\n return `Claim ${answer.id} has evidence '${evidence}' that is not bound to ${commit.slice(0, 12)}: ${committed.message}`;\n }\n const committedLines = committed.source.split(/\\r\\n|\\n|\\r/).length;\n if (line > committedLines) {\n return `Claim ${answer.id} has evidence '${evidence}', but '${relativePath}' has only ${committedLines} line(s) at ${commit.slice(0, 12)}.`;\n }\n } catch (err) {\n return `Claim ${answer.id} has unreadable evidence '${evidence}': ${err instanceof Error ? err.message : String(err)}`;\n }\n }\n }\n return null;\n}\n\ntype CommitResult = { ok: true; commit: string } | { ok: false; message: string };\ntype CommittedFileResult = { ok: true; source: string } | { ok: false; message: string };\n\n/** Require the evidence blob to exist at HEAD and have no uncommitted edits. */\nasync function committedFile(repoDir: string, commit: string, path: string): Promise<CommittedFileResult> {\n const clean = await git(repoDir, [\"diff\", \"--quiet\", commit, \"--\", path]);\n if (clean.code !== 0) {\n return {\n ok: false,\n message: clean.code === 1 ? `'${path}' has uncommitted changes` : clean.stderr || \"git diff failed\",\n };\n }\n const blob = await git(repoDir, [\"show\", `${commit}:${path}`]);\n if (blob.code !== 0) {\n return { ok: false, message: blob.stderr || `'${path}' is not tracked by that commit` };\n }\n return { ok: true, source: blob.stdout };\n}\n\ninterface GitResult {\n /**\n * Git's own exit status, or `-1` for \"git could not be run to completion\" —\n * no git on PATH, a spawn failure, or a child killed by a signal. Callers\n * read specific meaning into small numbers (`1` from `git diff --quiet` is\n * \"that file differs\"), so a run that never reached an exit status must not\n * borrow one of them.\n */\n code: number;\n stdout: string;\n stderr: string;\n}\n\nfunction git(repoDir: string, args: string[]): Promise<GitResult> {\n return new Promise((done) => {\n execFile(\"git\", [\"-C\", repoDir, ...args], { encoding: \"utf8\" }, (error, stdout, stderr) => {\n done({\n // `error.code` is an exit status only when it is a number: a spawn\n // failure reports an errno string (\"ENOENT\") and a signal-killed child\n // reports none at all. Folding those onto 1 would state that `git diff`\n // found a difference — a Ctrl-C or an OOM kill during\n // `loam verify --record` would read as an uncommitted edit and refuse\n // a sound attestation. -1 is not a status git assigns, so both diff\n // sites fall to their stderr-carrying arm and say what happened.\n code: error === null ? 0 : typeof error.code === \"number\" ? error.code : -1,\n stdout,\n // A child that never ran writes nothing to stderr, so its only account\n // of itself is on the error; without this the refusal would name no\n // cause at all.\n stderr: stderr.trim() || (error === null ? \"\" : error.message),\n });\n });\n });\n}\n\n/** Resolve HEAD without a shell, so repository paths remain data, never code. */\nasync function repositoryCommit(repoDir: string): Promise<CommitResult> {\n const result = await git(repoDir, [\"rev-parse\", \"--verify\", \"HEAD^{commit}\"]);\n if (result.code !== 0) {\n return {\n ok: false,\n message: `Federated verification requires a git repository with a committed HEAD: ${result.stderr || \"git rev-parse failed\"}`,\n };\n }\n const commit = result.stdout.trim();\n if (!/^[0-9a-f]{40,64}$/i.test(commit)) {\n return { ok: false, message: `Git returned an invalid HEAD commit '${commit}'.` };\n }\n return { ok: true, commit };\n}\n"]}
@@ -0,0 +1,343 @@
1
+ import { existsSync } from "node:fs";
2
+ import { loadConfig } from "../core/config.js";
3
+ import { emitJson, fail, NO_SERVICE_MESSAGE, repoPath, reportNoConfig } from "../core/json.js";
4
+ import { listField, parseFrontmatter, withFrontmatterFields } from "../core/frontmatter.js";
5
+ import { contentDigest, emptySourcesMessage, encodeSourceIndex, missingSources, patternSources, sourcesDigest, today, unsafeSources, } from "../core/provenance.js";
6
+ import { SPEC_AXES, servicePaths } from "../core/repo.js";
7
+ import { plural } from "./format.js";
8
+ import { message, NotUtf8Error, readUtf8, rollbackStaged, stageWrites, swapStaged, } from "../core/staging.js";
9
+ export function registerVouch(program) {
10
+ program
11
+ .command("vouch")
12
+ .description("Vouch for a service's living specs: stamp spec.md, and arch.spec.md when present, verified against the code they describe")
13
+ .option("--service <id>", "service to vouch for (defaults to the configured service)")
14
+ .option("--json", "emit the machine contract instead of the human view")
15
+ .action(async (opts) => {
16
+ const json = opts.json === true;
17
+ const config = await loadConfig();
18
+ if (!config) {
19
+ reportNoConfig(json);
20
+ return;
21
+ }
22
+ const service = opts.service ?? config.service;
23
+ if (service === undefined) {
24
+ return fail(json, "invalid-option", NO_SERVICE_MESSAGE);
25
+ }
26
+ // Vouching hashes the code the doc was written from, so it can only run
27
+ // where that code is. From anywhere else the paths are someone else's.
28
+ if (config.service !== service) {
29
+ const here = config.service === undefined ? "this is not a service repo" : `this repo is '${config.service}'`;
30
+ return fail(json, "invalid-option", `Cannot vouch for '${service}' from here: ${here}. \`sources\` resolve against the service's own repo — run it there.`);
31
+ }
32
+ const outcome = await vouch({
33
+ docsDir: config.docsDir,
34
+ service,
35
+ // The repo root, not the cwd. `sources:` are spelled relative to the
36
+ // repository — that is what they mean in the frontmatter and what
37
+ // `loam validate` resolves them against — so vouching from a
38
+ // subdirectory used to report every real source as missing
39
+ // (`sources-path-missing`) and refuse the stamp. `config.root` is the
40
+ // directory loam.json was found in, which is the only definition of
41
+ // "this repo" that does not move with the caller.
42
+ repoDir: config.root ?? process.cwd(),
43
+ today: today(new Date()),
44
+ });
45
+ if (!outcome.ok)
46
+ return fail(json, outcome.code, outcome.message);
47
+ const { spec, archSpec: arch } = outcome.stamped;
48
+ if (json) {
49
+ emitJson({
50
+ service,
51
+ path: repoPath(config.docsDir, spec.path),
52
+ status: outcome.status,
53
+ last_verified: outcome.lastVerified,
54
+ sources: spec.sources,
55
+ sources_digest: spec.digest,
56
+ content_digest: spec.contentDigest,
57
+ files: spec.files,
58
+ skipped: spec.skipped,
59
+ // The architecture axis, same keys: null when the service has no
60
+ // arch.spec.md, so a consumer can tell "none present" from an older
61
+ // loam that never reported the axis. status/last_verified are not
62
+ // repeated — the vouch is one act, and they hold for every file in it.
63
+ archSpec: arch === null
64
+ ? null
65
+ : {
66
+ path: repoPath(config.docsDir, arch.path),
67
+ sources: arch.sources,
68
+ sources_digest: arch.digest,
69
+ content_digest: arch.contentDigest,
70
+ files: arch.files,
71
+ skipped: arch.skipped,
72
+ },
73
+ });
74
+ return;
75
+ }
76
+ // spec.md first, arch.spec.md behind it when present — the order the
77
+ // person who vouched reads them in, and the order the axes are declared.
78
+ for (const [i, s] of [spec, ...(arch === null ? [] : [arch])].entries()) {
79
+ console.log(`${i > 0 ? "\n" : ""}${service} vouched — ${repoPath(config.docsDir, s.path)}\n`);
80
+ console.log(` status ${outcome.status}`);
81
+ console.log(` last_verified ${outcome.lastVerified}`);
82
+ console.log(` sources_digest ${s.digest} (${plural(s.files, "file")} from ${plural(s.sources.length, "source")})`);
83
+ console.log(` content_digest ${s.contentDigest}`);
84
+ // Said at the moment of stamping, not only later by `loam validate`:
85
+ // this is the one screen the person who vouched is actually looking at,
86
+ // and what it lists is the part of the tree their promise does not cover.
87
+ if (s.skipped.length > 0) {
88
+ console.log(`\n ⚠ ${plural(s.skipped.length, "path")} under those sources went unhashed:`);
89
+ for (const skip of s.skipped)
90
+ console.log(` ${skip.path} — ${skip.reason}`);
91
+ }
92
+ }
93
+ console.log(`\n\`loam validate\` will now say when that code moves out from under the spec — or when the spec moves under its own stamp.`);
94
+ });
95
+ }
96
+ /**
97
+ * Stamp `status`, `last_verified`, `sources_digest` and `content_digest` into
98
+ * a service's living specs — spec.md, and arch.spec.md beside it when the
99
+ * service has one — leaving every body byte-identical.
100
+ *
101
+ * Nothing is written unless all four can be stamped truthfully for EVERY file —
102
+ * a half-stamp (verified, but with no digest behind it; or one file stamped and
103
+ * its sibling not) is exactly the claim this command exists to stop being
104
+ * possible, so every present file is verified before any is written — and the
105
+ * writing itself is staged and swapped in like archive's merge, so a failure
106
+ * between the pair's two writes rolls the first back instead of leaving it.
107
+ * The two digests are the two halves of one promise: `sources_digest` pins the
108
+ * code that was read, `content_digest` pins the words it was read against, so
109
+ * `loam validate` can see either side move.
110
+ */
111
+ export async function vouch(req) {
112
+ const paths = servicePaths(req.docsDir, req.service);
113
+ if (!existsSync(paths.spec)) {
114
+ return {
115
+ ok: false,
116
+ code: "unknown-target",
117
+ message: `No living spec at ${paths.spec}. Run \`loam adopt\` for '${req.service}' first.`,
118
+ };
119
+ }
120
+ // Verify first, stamp after: spec.md is required (checked above), arch.spec.md
121
+ // rides when it exists, and one file that cannot be verified refuses the run
122
+ // before anything is written. The two axes are walked by name rather than
123
+ // accumulated in a loop, because everything downstream needs to know WHICH
124
+ // file it is holding — the required one or the optional one — and a loop can
125
+ // only say that positionally.
126
+ const [specAxis, archAxis] = SPEC_AXES;
127
+ const specVerified = await verifySpec(req, paths[specAxis.key], specAxis.file);
128
+ if (!specVerified.ok)
129
+ return specVerified;
130
+ let archVerified = null;
131
+ if (existsSync(paths[archAxis.key])) {
132
+ const outcome = await verifySpec(req, paths[archAxis.key], archAxis.file);
133
+ if (!outcome.ok)
134
+ return outcome;
135
+ archVerified = outcome;
136
+ }
137
+ const verified = [specVerified, ...(archVerified === null ? [] : [archVerified])];
138
+ const specPlan = planStamp(specVerified, req.today);
139
+ const archPlan = archVerified === null ? null : planStamp(archVerified, req.today);
140
+ const writes = [specPlan.write, ...(archPlan === null ? [] : [archPlan.write])];
141
+ // Commit through archive's stage-and-swap machinery (core/staging.ts) rather
142
+ // than a writeFile per file: every stamp is computed above in memory, so a
143
+ // plain sequential write could only die BETWEEN the pair's writes — spec.md
144
+ // verified, arch.spec.md still carrying the old stamp — the exact half-stamped
145
+ // state the all-or-nothing verification exists to rule out, lost at the last
146
+ // step to a full disk. Staging parks each file's new bytes beside it, swaps by
147
+ // rename(2), and on failure restores what already swapped from its pre-image.
148
+ const staged = await stageWrites(writes);
149
+ // One shared docs repo, ten service repos, and nothing stopping two of them
150
+ // from vouching at once. Between reading a spec and swapping the stamp in
151
+ // there is a window in which somebody else's vouch (or an editor, or a merge)
152
+ // can land in the same file — and because the new bytes were computed from
153
+ // the OLD ones, swapping them in would take that stamp back out without a
154
+ // word. `stageWrites` has already read what is on disk right now, so the
155
+ // check is a comparison, not another read: if the file is not what was
156
+ // verified, this run is describing a document that no longer exists.
157
+ // Bytes, not text: `stageWrites` reads the file as a Buffer (a string
158
+ // comparison against it is never equal, which made every vouch report
159
+ // `vouch-raced` and stamp nothing). A file that is GONE is still a race —
160
+ // `null` is not "empty", it is "the document this run described is not
161
+ // there any more".
162
+ // Joined by path, not by index: pairing the two lists positionally assumed
163
+ // `stageWrites` returns one entry per planned write in order — true today,
164
+ // promised nowhere, and a compare-and-set that silently compares the wrong
165
+ // pair would stamp over somebody else's vouch. A staged write no verified
166
+ // file claims has nothing to be compared against, so it counts as raced;
167
+ // fail-closed is the only safe direction here.
168
+ const verifiedByPath = new Map(verified.map((v) => [v.path, v]));
169
+ const raced = staged.filter((s) => {
170
+ const v = verifiedByPath.get(s.write.path);
171
+ return v === undefined || s.before === null || !s.before.equals(Buffer.from(v.raw, "utf8"));
172
+ });
173
+ if (raced.length > 0) {
174
+ // Nothing has swapped yet, so the rollback is only the temp files going
175
+ // away — the other writer's stamp is left exactly as it landed.
176
+ await rollbackStaged(staged);
177
+ return {
178
+ ok: false,
179
+ code: "vouch-raced",
180
+ message: `${req.service}: ${raced.map((s) => s.write.path).join(", ")} changed while this vouch was running — ` +
181
+ `another vouch or an edit landed first. Nothing was stamped: re-read the document and re-run.`,
182
+ };
183
+ }
184
+ try {
185
+ await swapStaged(staged);
186
+ }
187
+ catch (err) {
188
+ const failures = await rollbackStaged(staged);
189
+ // Archive's two answers to "can I trust the repo?", reused rather than
190
+ // minting vouch-only codes — a caller branches on the same fact either way:
191
+ // rolled back → nothing changed, re-running can work; incomplete → the
192
+ // files listed need a human. Only the prose is vouch's own.
193
+ return failures.length > 0
194
+ ? {
195
+ ok: false,
196
+ code: "rollback-incomplete",
197
+ message: `${message(err)} — ROLLBACK INCOMPLETE, these files may be half-stamped and need checking by hand: ${failures.join(", ")}`,
198
+ }
199
+ : {
200
+ ok: false,
201
+ code: "merge-failed",
202
+ message: `${message(err)} — the vouch was rolled back, no spec was stamped`,
203
+ };
204
+ }
205
+ return {
206
+ ok: true,
207
+ status: "verified",
208
+ lastVerified: req.today,
209
+ stamped: { spec: specPlan.stamped, archSpec: archPlan === null ? null : archPlan.stamped },
210
+ };
211
+ }
212
+ /**
213
+ * One verified file's share of the commit: the bytes to write, and the record
214
+ * of what was stamped into them. Kept beside each other because they are the
215
+ * same computation — the report must describe the file that was actually
216
+ * written, not a second guess at it.
217
+ */
218
+ function planStamp(v, lastVerified) {
219
+ // Two passes on purpose: `content_digest` hashes the body BELOW the
220
+ // frontmatter, and withFrontmatterFields promises that body byte-identical —
221
+ // so hashing after the first stamp and writing the hash in a second one
222
+ // yields a digest that is true of the file exactly as written. A re-vouch
223
+ // takes the same road and refreshes every field, this one included.
224
+ const restamped = withFrontmatterFields(v.raw, {
225
+ status: "verified",
226
+ last_verified: lastVerified,
227
+ sources_digest: v.digest,
228
+ // Beside the digest, what it was taken over. `sources_digest` alone can
229
+ // only ever say THAT the code moved; the next `loam validate` reads this
230
+ // back to say which files did.
231
+ sources_files: encodeSourceIndex(v.index),
232
+ });
233
+ const bodyDigest = contentDigest(restamped);
234
+ return {
235
+ write: { path: v.path, content: withFrontmatterFields(restamped, { content_digest: bodyDigest }) },
236
+ stamped: {
237
+ path: v.path,
238
+ file: v.file,
239
+ digest: v.digest,
240
+ contentDigest: bodyDigest,
241
+ sources: v.sources,
242
+ files: v.index.length,
243
+ skipped: v.skipped,
244
+ },
245
+ };
246
+ }
247
+ /**
248
+ * The per-file half of the refusal discipline: everything vouch cannot verify
249
+ * about ONE spec file, checked the same way for spec.md and arch.spec.md.
250
+ */
251
+ async function verifySpec(req, path, file) {
252
+ // Through readUtf8, so the round trip below is exact: the stamp is computed
253
+ // from `raw` and the race check compares `Buffer.from(raw)` against the bytes
254
+ // on disk, which only agree if the file decoded without substitutions. A
255
+ // non-UTF-8 spec is refused by name rather than stamped as verified with
256
+ // every undecodable byte replaced. Refused HERE rather than left to the CLI's
257
+ // top-level handler, which would report a diagnosable state as `internal`.
258
+ // What it replaces is a diagnosis that was worse than none: a UTF-16 spec
259
+ // decoded to a document with no frontmatter loam could find, so vouch refused
260
+ // it as `sources-absent` — "names no sources" — over a file whose `sources:`
261
+ // line is right there.
262
+ let raw;
263
+ try {
264
+ raw = await readUtf8(path);
265
+ }
266
+ catch (err) {
267
+ if (!(err instanceof NotUtf8Error))
268
+ throw err;
269
+ return {
270
+ ok: false,
271
+ code: "repository-unavailable",
272
+ message: `${req.service}: ${file} cannot be vouched for — ${err.message}`,
273
+ };
274
+ }
275
+ const fm = parseFrontmatter(raw);
276
+ // Before any sources reasoning: a header that does not parse hides whatever
277
+ // fields the author wrote, so "names no sources" would be a false diagnosis —
278
+ // and if the file ever reached the stamp, withFrontmatterFields' rule for
279
+ // unreadable headers is replace-don't-merge, which would silently discard the
280
+ // author's owner/service/sources lines. Refuse before the write path can see
281
+ // the file. The code stays `sources-absent` — for vouch's purposes the
282
+ // sources ARE unreadable-hence-absent, and the message carries the real
283
+ // diagnosis — rather than minting a new ErrorCode for one refusal.
284
+ if (fm.malformed) {
285
+ return {
286
+ ok: false,
287
+ code: "sources-absent",
288
+ message: `${req.service}: ${file} has a frontmatter block that cannot be read as YAML — ` +
289
+ `its fields (\`sources\` included) are unreadable, and stamping would rewrite the header wholesale, ` +
290
+ `losing what the author wrote. Fix the YAML between the \`---\` fences ` +
291
+ `(\`loam validate\` reports it as \`frontmatter.malformed\`), then re-run.`,
292
+ };
293
+ }
294
+ const sources = listField(fm, "sources");
295
+ if (sources.length === 0) {
296
+ return {
297
+ ok: false,
298
+ code: "sources-absent",
299
+ message: `${req.service}: ${file} names no sources — there is nothing to vouch it against. Add \`sources:\` naming the code it was written from.`,
300
+ };
301
+ }
302
+ // spec.md's refusals predate the second axis and keep their exact wording;
303
+ // where they did not name the file, only the newer axis adds it.
304
+ const label = file === "spec.md" ? req.service : `${req.service}: ${file}`;
305
+ // Before the existence check: a pattern is not a path that "does not exist",
306
+ // it is an entry loam no longer reads at all, and the refusal has to say so.
307
+ const patterns = patternSources(sources);
308
+ if (patterns.length > 0) {
309
+ return {
310
+ ok: false,
311
+ code: "sources-path-missing",
312
+ message: `${label}: ${patterns.length} source(s) are glob patterns — ${patterns.join(", ")}. Patterns are no longer supported: name files or directories (a directory already covers everything beneath it). A stamp over a pattern would vouch for a file set nobody can be sure of.`,
313
+ };
314
+ }
315
+ const unsafe = unsafeSources(req.repoDir, sources);
316
+ if (unsafe.length > 0) {
317
+ return {
318
+ ok: false,
319
+ code: "sources-path-missing",
320
+ message: `${label}: ${unsafe.length} source(s) escape the service repo — ${unsafe.join(", ")}. Sources must be relative paths contained by this repository, including through symlinks; vouch will not hash files outside it.`,
321
+ };
322
+ }
323
+ const missing = missingSources(req.repoDir, sources);
324
+ if (missing.length > 0) {
325
+ return {
326
+ ok: false,
327
+ code: "sources-path-missing",
328
+ message: `${label}: ${missing.length} source(s) do not exist — ${missing.join(", ")}. Vouching now would stamp a claim about code that is not there.`,
329
+ };
330
+ }
331
+ const { digest, index, skipped } = await sourcesDigest(req.repoDir, sources);
332
+ if (index.length === 0) {
333
+ // A directory that exists but holds no files — or only ones the walk leaves
334
+ // out: dot-entries, `node_modules`, anything the repository itself ignores.
335
+ // The paths "resolve", but a digest over nothing never changes, so the stamp
336
+ // would read as current forever. The sentence comes from provenance.ts so
337
+ // that `loam validate` can grade the same state in the same words instead of
338
+ // going green on a document this command refuses.
339
+ return { ok: false, code: "sources-absent", message: emptySourcesMessage(label, sources) };
340
+ }
341
+ return { ok: true, path, file, raw, sources, digest, index, skipped };
342
+ }
343
+ //# sourceMappingURL=vouch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vouch.js","sourceRoot":"","sources":["../../src/commands/vouch.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam vouch` — the human act SCHEMA.md has always described as \"promote draft\n * -> verified\".\n *\n * Everything else loam checks is internal consistency, and an agent writing\n * fluent prose satisfies all of it. This is the one command that records a\n * person: they read the code, they say the document matches it, and loam stamps\n * a digest of what they read — and a digest of the document they read it\n * against, so neither side can move quietly. From then on `loam validate` can\n * tell the difference between a document nobody has checked, one that still\n * matches the code, one the code has moved out from under, and one whose own\n * prose changed after the stamp.\n *\n * The stamp is only worth what it claims, so vouch refuses everything it cannot\n * actually verify — a frontmatter block that will not parse (whose fields\n * nobody can read, and whose rewrite would lose the author's lines), a spec\n * with no sources, a glob pattern (no longer supported), a source that is\n * gone, a directory holding no files, or a repo that is not this service's —\n * and refuses without writing anything.\n *\n * \"The document\" is every spec-axis file the service has: spec.md always, and\n * arch.spec.md beside it when present — same frontmatter conventions, same\n * stamp, one vouch. The refusal discipline is per file but the vouch is\n * all-or-nothing per service: one unverifiable file refuses the whole run, so\n * a `verified` service never means \"half-stamped\".\n */\nimport type { Command } from \"commander\";\nimport { existsSync } from \"node:fs\";\nimport { loadConfig } from \"../core/config.js\";\nimport { emitJson, fail, NO_SERVICE_MESSAGE, repoPath, reportNoConfig, type ErrorCode } from \"../core/json.js\";\nimport { listField, parseFrontmatter, withFrontmatterFields } from \"../core/frontmatter.js\";\nimport {\n contentDigest,\n emptySourcesMessage,\n encodeSourceIndex,\n missingSources,\n patternSources,\n sourcesDigest,\n today,\n unsafeSources,\n type SkippedSource,\n type SourceIndexEntry,\n} from \"../core/provenance.js\";\nimport { SPEC_AXES, servicePaths } from \"../core/repo.js\";\nimport { plural } from \"./format.js\";\nimport {\n message,\n NotUtf8Error,\n readUtf8,\n rollbackStaged,\n stageWrites,\n swapStaged,\n type PlannedWrite,\n} from \"../core/staging.js\";\n\ninterface VouchOptions {\n service?: string;\n json?: boolean;\n}\n\nexport interface VouchRequest {\n docsDir: string;\n service: string;\n /** The service's own repo — what `sources` resolve against. */\n repoDir: string;\n /** The date to stamp. Injected rather than read off the clock, so it can be pinned. */\n today: string;\n}\n\n/** One spec-axis file's share of a successful vouch. */\nexport interface StampedSpec {\n /** Absolute path of the file that was stamped. */\n path: string;\n /** The axis's filename — \"spec.md\" or \"arch.spec.md\". */\n file: string;\n /** Digest of the sources, as stamped into `sources_digest`. */\n digest: string;\n /** Digest of the document's own body, as stamped into `content_digest`. */\n contentDigest: string;\n /** The `sources` entries, as written in the frontmatter. */\n sources: string[];\n /** How many files those entries expanded to. */\n files: number;\n /**\n * Paths under those entries the digest would not hash. A vouch over a spec\n * with a skipped path is still a vouch — the person read what they read — but\n * they are told, because the stamp cannot go stale over bytes it never saw.\n */\n skipped: SkippedSource[];\n}\n\nexport type VouchOutcome =\n | {\n ok: true;\n status: \"verified\";\n lastVerified: string;\n /**\n * Every spec-axis file stamped. Named, not ordered: spec.md is required\n * for a vouch to happen at all, and arch.spec.md is the only other file\n * one can touch, so the type is what says which is which. An array said it\n * in a comment instead, and every reader had to assert non-emptiness to\n * reach the file that comment promised was there.\n */\n stamped: { spec: StampedSpec; archSpec: StampedSpec | null };\n }\n | {\n ok: false;\n // The last three are the commit phase failing: `vouch-raced` says the\n // document moved under the run and nothing was written at all,\n // `merge-failed` says the rollback held (nothing was stamped, re-running\n // can work), `rollback-incomplete` says it did not and the message lists\n // the files.\n code: Extract<\n ErrorCode,\n | \"unknown-target\"\n | \"sources-absent\"\n | \"sources-path-missing\"\n // The spec is there but is not UTF-8 text: a diagnosable state, not the\n // unexpected throw `internal` is for.\n | \"repository-unavailable\"\n | \"vouch-raced\"\n | \"merge-failed\"\n | \"rollback-incomplete\"\n >;\n message: string;\n };\n\nexport function registerVouch(program: Command): void {\n program\n .command(\"vouch\")\n .description(\n \"Vouch for a service's living specs: stamp spec.md, and arch.spec.md when present, verified against the code they describe\",\n )\n .option(\"--service <id>\", \"service to vouch for (defaults to the configured service)\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (opts: VouchOptions) => {\n const json = opts.json === true;\n\n const config = await loadConfig();\n if (!config) {\n reportNoConfig(json);\n return;\n }\n\n const service = opts.service ?? config.service;\n if (service === undefined) {\n return fail(json, \"invalid-option\", NO_SERVICE_MESSAGE);\n }\n // Vouching hashes the code the doc was written from, so it can only run\n // where that code is. From anywhere else the paths are someone else's.\n if (config.service !== service) {\n const here =\n config.service === undefined ? \"this is not a service repo\" : `this repo is '${config.service}'`;\n return fail(\n json,\n \"invalid-option\",\n `Cannot vouch for '${service}' from here: ${here}. \\`sources\\` resolve against the service's own repo — run it there.`,\n );\n }\n\n const outcome = await vouch({\n docsDir: config.docsDir,\n service,\n // The repo root, not the cwd. `sources:` are spelled relative to the\n // repository — that is what they mean in the frontmatter and what\n // `loam validate` resolves them against — so vouching from a\n // subdirectory used to report every real source as missing\n // (`sources-path-missing`) and refuse the stamp. `config.root` is the\n // directory loam.json was found in, which is the only definition of\n // \"this repo\" that does not move with the caller.\n repoDir: config.root ?? process.cwd(),\n today: today(new Date()),\n });\n if (!outcome.ok) return fail(json, outcome.code, outcome.message);\n\n const { spec, archSpec: arch } = outcome.stamped;\n if (json) {\n emitJson({\n service,\n path: repoPath(config.docsDir, spec.path),\n status: outcome.status,\n last_verified: outcome.lastVerified,\n sources: spec.sources,\n sources_digest: spec.digest,\n content_digest: spec.contentDigest,\n files: spec.files,\n skipped: spec.skipped,\n // The architecture axis, same keys: null when the service has no\n // arch.spec.md, so a consumer can tell \"none present\" from an older\n // loam that never reported the axis. status/last_verified are not\n // repeated — the vouch is one act, and they hold for every file in it.\n archSpec:\n arch === null\n ? null\n : {\n path: repoPath(config.docsDir, arch.path),\n sources: arch.sources,\n sources_digest: arch.digest,\n content_digest: arch.contentDigest,\n files: arch.files,\n skipped: arch.skipped,\n },\n });\n return;\n }\n // spec.md first, arch.spec.md behind it when present — the order the\n // person who vouched reads them in, and the order the axes are declared.\n for (const [i, s] of [spec, ...(arch === null ? [] : [arch])].entries()) {\n console.log(`${i > 0 ? \"\\n\" : \"\"}${service} vouched — ${repoPath(config.docsDir, s.path)}\\n`);\n console.log(` status ${outcome.status}`);\n console.log(` last_verified ${outcome.lastVerified}`);\n console.log(\n ` sources_digest ${s.digest} (${plural(s.files, \"file\")} from ${plural(s.sources.length, \"source\")})`,\n );\n console.log(` content_digest ${s.contentDigest}`);\n // Said at the moment of stamping, not only later by `loam validate`:\n // this is the one screen the person who vouched is actually looking at,\n // and what it lists is the part of the tree their promise does not cover.\n if (s.skipped.length > 0) {\n console.log(`\\n ⚠ ${plural(s.skipped.length, \"path\")} under those sources went unhashed:`);\n for (const skip of s.skipped) console.log(` ${skip.path} — ${skip.reason}`);\n }\n }\n console.log(\n `\\n\\`loam validate\\` will now say when that code moves out from under the spec — or when the spec moves under its own stamp.`,\n );\n });\n}\n\n/**\n * Stamp `status`, `last_verified`, `sources_digest` and `content_digest` into\n * a service's living specs — spec.md, and arch.spec.md beside it when the\n * service has one — leaving every body byte-identical.\n *\n * Nothing is written unless all four can be stamped truthfully for EVERY file —\n * a half-stamp (verified, but with no digest behind it; or one file stamped and\n * its sibling not) is exactly the claim this command exists to stop being\n * possible, so every present file is verified before any is written — and the\n * writing itself is staged and swapped in like archive's merge, so a failure\n * between the pair's two writes rolls the first back instead of leaving it.\n * The two digests are the two halves of one promise: `sources_digest` pins the\n * code that was read, `content_digest` pins the words it was read against, so\n * `loam validate` can see either side move.\n */\nexport async function vouch(req: VouchRequest): Promise<VouchOutcome> {\n const paths = servicePaths(req.docsDir, req.service);\n if (!existsSync(paths.spec)) {\n return {\n ok: false,\n code: \"unknown-target\",\n message: `No living spec at ${paths.spec}. Run \\`loam adopt\\` for '${req.service}' first.`,\n };\n }\n\n // Verify first, stamp after: spec.md is required (checked above), arch.spec.md\n // rides when it exists, and one file that cannot be verified refuses the run\n // before anything is written. The two axes are walked by name rather than\n // accumulated in a loop, because everything downstream needs to know WHICH\n // file it is holding — the required one or the optional one — and a loop can\n // only say that positionally.\n const [specAxis, archAxis] = SPEC_AXES;\n const specVerified = await verifySpec(req, paths[specAxis.key], specAxis.file);\n if (!specVerified.ok) return specVerified;\n let archVerified: VerifiedSpec | null = null;\n if (existsSync(paths[archAxis.key])) {\n const outcome = await verifySpec(req, paths[archAxis.key], archAxis.file);\n if (!outcome.ok) return outcome;\n archVerified = outcome;\n }\n const verified: VerifiedSpec[] = [specVerified, ...(archVerified === null ? [] : [archVerified])];\n\n const specPlan = planStamp(specVerified, req.today);\n const archPlan = archVerified === null ? null : planStamp(archVerified, req.today);\n const writes: PlannedWrite[] = [specPlan.write, ...(archPlan === null ? [] : [archPlan.write])];\n\n // Commit through archive's stage-and-swap machinery (core/staging.ts) rather\n // than a writeFile per file: every stamp is computed above in memory, so a\n // plain sequential write could only die BETWEEN the pair's writes — spec.md\n // verified, arch.spec.md still carrying the old stamp — the exact half-stamped\n // state the all-or-nothing verification exists to rule out, lost at the last\n // step to a full disk. Staging parks each file's new bytes beside it, swaps by\n // rename(2), and on failure restores what already swapped from its pre-image.\n const staged = await stageWrites(writes);\n\n // One shared docs repo, ten service repos, and nothing stopping two of them\n // from vouching at once. Between reading a spec and swapping the stamp in\n // there is a window in which somebody else's vouch (or an editor, or a merge)\n // can land in the same file — and because the new bytes were computed from\n // the OLD ones, swapping them in would take that stamp back out without a\n // word. `stageWrites` has already read what is on disk right now, so the\n // check is a comparison, not another read: if the file is not what was\n // verified, this run is describing a document that no longer exists.\n // Bytes, not text: `stageWrites` reads the file as a Buffer (a string\n // comparison against it is never equal, which made every vouch report\n // `vouch-raced` and stamp nothing). A file that is GONE is still a race —\n // `null` is not \"empty\", it is \"the document this run described is not\n // there any more\".\n // Joined by path, not by index: pairing the two lists positionally assumed\n // `stageWrites` returns one entry per planned write in order — true today,\n // promised nowhere, and a compare-and-set that silently compares the wrong\n // pair would stamp over somebody else's vouch. A staged write no verified\n // file claims has nothing to be compared against, so it counts as raced;\n // fail-closed is the only safe direction here.\n const verifiedByPath = new Map(verified.map((v) => [v.path, v] as const));\n const raced = staged.filter((s) => {\n const v = verifiedByPath.get(s.write.path);\n return v === undefined || s.before === null || !s.before.equals(Buffer.from(v.raw, \"utf8\"));\n });\n if (raced.length > 0) {\n // Nothing has swapped yet, so the rollback is only the temp files going\n // away — the other writer's stamp is left exactly as it landed.\n await rollbackStaged(staged);\n return {\n ok: false,\n code: \"vouch-raced\",\n message:\n `${req.service}: ${raced.map((s) => s.write.path).join(\", \")} changed while this vouch was running — ` +\n `another vouch or an edit landed first. Nothing was stamped: re-read the document and re-run.`,\n };\n }\n\n try {\n await swapStaged(staged);\n } catch (err) {\n const failures = await rollbackStaged(staged);\n // Archive's two answers to \"can I trust the repo?\", reused rather than\n // minting vouch-only codes — a caller branches on the same fact either way:\n // rolled back → nothing changed, re-running can work; incomplete → the\n // files listed need a human. Only the prose is vouch's own.\n return failures.length > 0\n ? {\n ok: false,\n code: \"rollback-incomplete\",\n message: `${message(err)} — ROLLBACK INCOMPLETE, these files may be half-stamped and need checking by hand: ${failures.join(\", \")}`,\n }\n : {\n ok: false,\n code: \"merge-failed\",\n message: `${message(err)} — the vouch was rolled back, no spec was stamped`,\n };\n }\n return {\n ok: true,\n status: \"verified\",\n lastVerified: req.today,\n stamped: { spec: specPlan.stamped, archSpec: archPlan === null ? null : archPlan.stamped },\n };\n}\n\n/**\n * One verified file's share of the commit: the bytes to write, and the record\n * of what was stamped into them. Kept beside each other because they are the\n * same computation — the report must describe the file that was actually\n * written, not a second guess at it.\n */\nfunction planStamp(v: VerifiedSpec, lastVerified: string): { write: PlannedWrite; stamped: StampedSpec } {\n // Two passes on purpose: `content_digest` hashes the body BELOW the\n // frontmatter, and withFrontmatterFields promises that body byte-identical —\n // so hashing after the first stamp and writing the hash in a second one\n // yields a digest that is true of the file exactly as written. A re-vouch\n // takes the same road and refreshes every field, this one included.\n const restamped = withFrontmatterFields(v.raw, {\n status: \"verified\",\n last_verified: lastVerified,\n sources_digest: v.digest,\n // Beside the digest, what it was taken over. `sources_digest` alone can\n // only ever say THAT the code moved; the next `loam validate` reads this\n // back to say which files did.\n sources_files: encodeSourceIndex(v.index),\n });\n const bodyDigest = contentDigest(restamped);\n return {\n write: { path: v.path, content: withFrontmatterFields(restamped, { content_digest: bodyDigest }) },\n stamped: {\n path: v.path,\n file: v.file,\n digest: v.digest,\n contentDigest: bodyDigest,\n sources: v.sources,\n files: v.index.length,\n skipped: v.skipped,\n },\n };\n}\n\n/** A spec-axis file whose sources all check out, carrying what the stamp needs. */\ninterface VerifiedSpec {\n ok: true;\n path: string;\n file: string;\n /** The file exactly as read — what the stamp is applied to. */\n raw: string;\n sources: string[];\n digest: string;\n /** Per-file shas, in digest order — what `sources_files` is stamped from. */\n index: SourceIndexEntry[];\n skipped: SkippedSource[];\n}\n\n/**\n * The per-file half of the refusal discipline: everything vouch cannot verify\n * about ONE spec file, checked the same way for spec.md and arch.spec.md.\n */\nasync function verifySpec(\n req: VouchRequest,\n path: string,\n file: string,\n): Promise<VerifiedSpec | Extract<VouchOutcome, { ok: false }>> {\n // Through readUtf8, so the round trip below is exact: the stamp is computed\n // from `raw` and the race check compares `Buffer.from(raw)` against the bytes\n // on disk, which only agree if the file decoded without substitutions. A\n // non-UTF-8 spec is refused by name rather than stamped as verified with\n // every undecodable byte replaced. Refused HERE rather than left to the CLI's\n // top-level handler, which would report a diagnosable state as `internal`.\n // What it replaces is a diagnosis that was worse than none: a UTF-16 spec\n // decoded to a document with no frontmatter loam could find, so vouch refused\n // it as `sources-absent` — \"names no sources\" — over a file whose `sources:`\n // line is right there.\n let raw: string;\n try {\n raw = await readUtf8(path);\n } catch (err) {\n if (!(err instanceof NotUtf8Error)) throw err;\n return {\n ok: false,\n code: \"repository-unavailable\",\n message: `${req.service}: ${file} cannot be vouched for — ${err.message}`,\n };\n }\n const fm = parseFrontmatter(raw);\n // Before any sources reasoning: a header that does not parse hides whatever\n // fields the author wrote, so \"names no sources\" would be a false diagnosis —\n // and if the file ever reached the stamp, withFrontmatterFields' rule for\n // unreadable headers is replace-don't-merge, which would silently discard the\n // author's owner/service/sources lines. Refuse before the write path can see\n // the file. The code stays `sources-absent` — for vouch's purposes the\n // sources ARE unreadable-hence-absent, and the message carries the real\n // diagnosis — rather than minting a new ErrorCode for one refusal.\n if (fm.malformed) {\n return {\n ok: false,\n code: \"sources-absent\",\n message:\n `${req.service}: ${file} has a frontmatter block that cannot be read as YAML — ` +\n `its fields (\\`sources\\` included) are unreadable, and stamping would rewrite the header wholesale, ` +\n `losing what the author wrote. Fix the YAML between the \\`---\\` fences ` +\n `(\\`loam validate\\` reports it as \\`frontmatter.malformed\\`), then re-run.`,\n };\n }\n const sources = listField(fm, \"sources\");\n if (sources.length === 0) {\n return {\n ok: false,\n code: \"sources-absent\",\n message: `${req.service}: ${file} names no sources — there is nothing to vouch it against. Add \\`sources:\\` naming the code it was written from.`,\n };\n }\n\n // spec.md's refusals predate the second axis and keep their exact wording;\n // where they did not name the file, only the newer axis adds it.\n const label = file === \"spec.md\" ? req.service : `${req.service}: ${file}`;\n\n // Before the existence check: a pattern is not a path that \"does not exist\",\n // it is an entry loam no longer reads at all, and the refusal has to say so.\n const patterns = patternSources(sources);\n if (patterns.length > 0) {\n return {\n ok: false,\n code: \"sources-path-missing\",\n message: `${label}: ${patterns.length} source(s) are glob patterns — ${patterns.join(\", \")}. Patterns are no longer supported: name files or directories (a directory already covers everything beneath it). A stamp over a pattern would vouch for a file set nobody can be sure of.`,\n };\n }\n\n const unsafe = unsafeSources(req.repoDir, sources);\n if (unsafe.length > 0) {\n return {\n ok: false,\n code: \"sources-path-missing\",\n message: `${label}: ${unsafe.length} source(s) escape the service repo — ${unsafe.join(\", \")}. Sources must be relative paths contained by this repository, including through symlinks; vouch will not hash files outside it.`,\n };\n }\n\n const missing = missingSources(req.repoDir, sources);\n if (missing.length > 0) {\n return {\n ok: false,\n code: \"sources-path-missing\",\n message: `${label}: ${missing.length} source(s) do not exist — ${missing.join(\", \")}. Vouching now would stamp a claim about code that is not there.`,\n };\n }\n\n const { digest, index, skipped } = await sourcesDigest(req.repoDir, sources);\n if (index.length === 0) {\n // A directory that exists but holds no files — or only ones the walk leaves\n // out: dot-entries, `node_modules`, anything the repository itself ignores.\n // The paths \"resolve\", but a digest over nothing never changes, so the stamp\n // would read as current forever. The sentence comes from provenance.ts so\n // that `loam validate` can grade the same state in the same words instead of\n // going green on a document this command refuses.\n return { ok: false, code: \"sources-absent\", message: emptySourcesMessage(label, sources) };\n }\n return { ok: true, path, file, raw, sources, digest, index, skipped };\n}\n"]}