@ybotok/loam 0.1.0-beta.1 → 0.1.0-beta.3

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 (120) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +6 -1
  3. package/SCHEMA.md +22 -4
  4. package/dist/cli.js +4 -0
  5. package/dist/cli.js.map +1 -1
  6. package/dist/commands/adopt.js +17 -3
  7. package/dist/commands/adopt.js.map +1 -1
  8. package/dist/commands/archive.js +24 -8
  9. package/dist/commands/archive.js.map +1 -1
  10. package/dist/commands/delta.js +74 -47
  11. package/dist/commands/delta.js.map +1 -1
  12. package/dist/commands/dependencies.js +7 -2
  13. package/dist/commands/dependencies.js.map +1 -1
  14. package/dist/commands/docs-repo-gate.js +104 -0
  15. package/dist/commands/docs-repo-gate.js.map +1 -0
  16. package/dist/commands/explore.js +195 -0
  17. package/dist/commands/explore.js.map +1 -0
  18. package/dist/commands/format.js +17 -0
  19. package/dist/commands/format.js.map +1 -0
  20. package/dist/commands/gherkin.js +87 -48
  21. package/dist/commands/gherkin.js.map +1 -1
  22. package/dist/commands/init.js +3 -3
  23. package/dist/commands/init.js.map +1 -1
  24. package/dist/commands/instructions.js +80 -0
  25. package/dist/commands/instructions.js.map +1 -0
  26. package/dist/commands/list.js +10 -76
  27. package/dist/commands/list.js.map +1 -1
  28. package/dist/commands/migrate-openspec.js +45 -28
  29. package/dist/commands/migrate-openspec.js.map +1 -1
  30. package/dist/commands/new.js +35 -34
  31. package/dist/commands/new.js.map +1 -1
  32. package/dist/commands/rebase.js +2 -4
  33. package/dist/commands/rebase.js.map +1 -1
  34. package/dist/commands/show.js +26 -31
  35. package/dist/commands/show.js.map +1 -1
  36. package/dist/commands/status.js +17 -25
  37. package/dist/commands/status.js.map +1 -1
  38. package/dist/commands/unarchive.js +12 -5
  39. package/dist/commands/unarchive.js.map +1 -1
  40. package/dist/commands/validate.js +306 -142
  41. package/dist/commands/validate.js.map +1 -1
  42. package/dist/commands/verify.js +33 -17
  43. package/dist/commands/verify.js.map +1 -1
  44. package/dist/commands/vouch.js +156 -60
  45. package/dist/commands/vouch.js.map +1 -1
  46. package/dist/core/agent.js +392 -42
  47. package/dist/core/agent.js.map +1 -1
  48. package/dist/core/agents-stamp.js +47 -10
  49. package/dist/core/agents-stamp.js.map +1 -1
  50. package/dist/core/arch.js +28 -2
  51. package/dist/core/arch.js.map +1 -1
  52. package/dist/core/asyncapi.js +253 -0
  53. package/dist/core/asyncapi.js.map +1 -0
  54. package/dist/core/brief.js +28 -6
  55. package/dist/core/brief.js.map +1 -1
  56. package/dist/core/coherence.js +37 -13
  57. package/dist/core/coherence.js.map +1 -1
  58. package/dist/core/concurrency.js +51 -0
  59. package/dist/core/concurrency.js.map +1 -0
  60. package/dist/core/config.js +10 -2
  61. package/dist/core/config.js.map +1 -1
  62. package/dist/core/delta.js +21 -2
  63. package/dist/core/delta.js.map +1 -1
  64. package/dist/core/dependencies.js +46 -42
  65. package/dist/core/dependencies.js.map +1 -1
  66. package/dist/core/docs.js +65 -3
  67. package/dist/core/docs.js.map +1 -1
  68. package/dist/core/doctor.js +79 -27
  69. package/dist/core/doctor.js.map +1 -1
  70. package/dist/core/document-bytes.js +59 -0
  71. package/dist/core/document-bytes.js.map +1 -0
  72. package/dist/core/explore.js +225 -0
  73. package/dist/core/explore.js.map +1 -0
  74. package/dist/core/fleet-context.js +29 -63
  75. package/dist/core/fleet-context.js.map +1 -1
  76. package/dist/core/frontmatter.js +1 -5
  77. package/dist/core/frontmatter.js.map +1 -1
  78. package/dist/core/gherkin.js +3 -61
  79. package/dist/core/gherkin.js.map +1 -1
  80. package/dist/core/health.js +1 -5
  81. package/dist/core/health.js.map +1 -1
  82. package/dist/core/ids.js +34 -5
  83. package/dist/core/ids.js.map +1 -1
  84. package/dist/core/json.js +8 -1
  85. package/dist/core/json.js.map +1 -1
  86. package/dist/core/likec4.js +19 -4
  87. package/dist/core/likec4.js.map +1 -1
  88. package/dist/core/maturity.js +66 -0
  89. package/dist/core/maturity.js.map +1 -0
  90. package/dist/core/openapi-merge.js +39 -6
  91. package/dist/core/openapi-merge.js.map +1 -1
  92. package/dist/core/openapi.js +17 -5
  93. package/dist/core/openapi.js.map +1 -1
  94. package/dist/core/openspec-inventory.js +64 -27
  95. package/dist/core/openspec-inventory.js.map +1 -1
  96. package/dist/core/path-safety.js +59 -0
  97. package/dist/core/path-safety.js.map +1 -1
  98. package/dist/core/provenance.js +102 -10
  99. package/dist/core/provenance.js.map +1 -1
  100. package/dist/core/records.js +33 -0
  101. package/dist/core/records.js.map +1 -0
  102. package/dist/core/repo.js +77 -1
  103. package/dist/core/repo.js.map +1 -1
  104. package/dist/core/report.js +9 -0
  105. package/dist/core/report.js.map +1 -1
  106. package/dist/core/results.js +1 -3
  107. package/dist/core/results.js.map +1 -1
  108. package/dist/core/spec.js +28 -4
  109. package/dist/core/spec.js.map +1 -1
  110. package/dist/core/staging.js +124 -17
  111. package/dist/core/staging.js.map +1 -1
  112. package/dist/core/status.js +152 -35
  113. package/dist/core/status.js.map +1 -1
  114. package/dist/core/steps.js +72 -0
  115. package/dist/core/steps.js.map +1 -0
  116. package/dist/core/verify.js +18 -4
  117. package/dist/core/verify.js.map +1 -1
  118. package/dist/core/version.js +17 -1
  119. package/dist/core/version.js.map +1 -1
  120. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"openapi-merge.js","sourceRoot":"","sources":["../../src/core/openapi-merge.ts"],"names":[],"mappings":"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","sourcesContent":["import { isDeepStrictEqual } from \"node:util\";\nimport { isMap, parseDocument } from \"yaml\";\nimport {\n HTTP_METHODS,\n OPENAPI_BASELINE_KEY,\n classifyBaselineDigests,\n operationBaselineOf,\n operationDigest,\n withoutOperationBaseline,\n} from \"./openapi.js\";\n\n/**\n * What a feature's operation IS, relative to the living contract — the one\n * judgement that tells a QUOTE from an EDIT.\n *\n * A feature's openapi.yaml is a complete document, so most of what it spells is\n * quotation: the author restated the living contract around the slot they were\n * changing. Without a pin the merge cannot tell the two apart and upserts both,\n * which is how a feature that only meant to touch `cancelOrder` silently\n * reverted another team's landed change to `refundOrder` — no overlap between\n * the features required, just two deltas over one service.\n */\nexport type OperationBaselineVerdict = ReturnType<typeof classifyBaselineDigests>;\n\n/**\n * Classify one feature operation against its living counterpart, from the raw\n * YAML trees the merge works in. The rule itself lives in core/openapi.ts, so\n * the gate and the merge cannot drift apart about which operations get written.\n *\n * A malformed pin matches no digest and lands in `stale`, which merges rather\n * than skips. Deliberate: `openapi.baseline-invalid` refuses it at the gate, and\n * a value nobody can evaluate must never be the reason a merge quietly drops an\n * operation.\n */\nexport function classifyOperationBaseline(featureOp: unknown, livingOp: unknown): OperationBaselineVerdict {\n return classifyBaselineDigests(\n operationBaselineOf(featureOp),\n operationDigest(featureOp),\n livingOp === undefined ? undefined : operationDigest(livingOp),\n );\n}\n\n/** What happened to one operation's `x-loam-based-on`. */\nexport interface OperationPin {\n path: string;\n method: string;\n /** Empty when the operation declares none — the slot is still the identity. */\n operationId: string;\n status:\n /** It had no pin and now has one. */\n | \"pinned\"\n /** It named an older living version; it now names the current one. */\n | \"repinned\"\n /** It already named the current living version — no write. */\n | \"unchanged\"\n /** The living contract has no operation at this slot: this feature is adding it. */\n | \"unresolved\"\n /** Written as a YAML alias — one shared value backs every use, so loam will not stamp through it. */\n | \"unwritable\";\n from: string | null;\n to: string | null;\n}\n\nexport interface OpenapiPinPlan {\n /** The rewritten contract, or null when no pin changes. */\n text: string | null;\n pins: OperationPin[];\n}\n\n/**\n * Pin every operation in a feature contract against the living one — what\n * `loam rebase` writes on this axis.\n *\n * The pin is always `operationDigest` of the LIVING operation, never of the\n * delta's own, and that single rule produces both merge verdicts by itself: an\n * operation the author only QUOTED is byte-equal to living, so its pin equals\n * its own content and `classifyOperationBaseline` calls it a quote; one the\n * author EDITED differs from its pin, so the merge writes it. Nothing has to\n * guess at intent — the document already records it, and the pin makes it\n * legible to the merge.\n *\n * Slot-keyed (path + method), exactly as the merge upserts. An operationId that\n * moved to another slot is a new slot with nothing living behind it, which is\n * `unresolved` and correct: there is no living version of an operation at a\n * path the contract does not serve yet.\n */\nexport function pinOpenapiOperations(featureText: string, livingText: string, service: string): OpenapiPinPlan {\n const doc = parseDocument(featureText);\n if (doc.errors.length > 0) throw new OpenapiMergeError(\"feature\", service, doc.errors[0]!.message);\n let featPlain: unknown;\n let livingPlain: unknown;\n try {\n featPlain = doc.toJS() ?? {};\n } catch (error) {\n throw new OpenapiMergeError(\"feature\", service, errorMessage(error));\n }\n try {\n const parsed = parseDocument(livingText);\n if (parsed.errors.length > 0) throw new OpenapiMergeError(\"living\", service, parsed.errors[0]!.message);\n livingPlain = parsed.toJS() ?? {};\n } catch (error) {\n if (error instanceof OpenapiMergeError) throw error;\n throw new OpenapiMergeError(\"living\", service, errorMessage(error));\n }\n\n const featPaths = plainChild(featPlain, \"paths\");\n if (!isRecord(featPaths)) return { text: null, pins: [] };\n const livingPaths = plainChild(livingPlain, \"paths\");\n\n const pins: OperationPin[] = [];\n let edited = false;\n for (const [path, item] of Object.entries(featPaths)) {\n if (!isRecord(item)) continue;\n for (const [method, op] of Object.entries(item)) {\n if (!HTTP_METHODS.has(method) || !isRecord(op)) continue;\n // A removal marker asserts a slot rather than restating an operation, so\n // it has nothing to be based on; `openapi.remove-target-mismatch` is what\n // guards it against a living slot that moved under it.\n if (isRemoval(op)) continue;\n\n const id = typeof op[\"operationId\"] === \"string\" ? op[\"operationId\"] : \"\";\n const at = { path, method, operationId: id };\n const from = operationBaselineOf(op) ?? null;\n const livingItem = plainChild(livingPaths, path);\n const livingOp = isRecord(livingItem) ? livingItem[method] : undefined;\n if (!isRecord(livingOp)) {\n pins.push({ ...at, status: \"unresolved\", from, to: null });\n continue;\n }\n const digest = operationDigest(livingOp);\n if (from === digest) {\n pins.push({ ...at, status: \"unchanged\", from: digest, to: digest });\n continue;\n }\n // One shared value backs every use of an anchor, so `setIn` through an\n // alias would pin every other use of it too — the same reason\n // stripOpenapiRemovalMarkers refuses to edit aliases in place.\n if (!isMap(doc.getIn([\"paths\", path, method]))) {\n pins.push({ ...at, status: \"unwritable\", from, to: null });\n continue;\n }\n pins.push({ ...at, status: from === null ? \"pinned\" : \"repinned\", from, to: digest });\n doc.setIn([\"paths\", path, method, OPENAPI_BASELINE_KEY], digest);\n edited = true;\n }\n }\n\n if (!edited) return { text: null, pins };\n try {\n return { text: doc.toString(), pins };\n } catch (error) {\n throw new OpenapiMergeError(\"feature\", service, errorMessage(error));\n }\n}\n\n/** What an OpenAPI path merge computed, including every condition the caller must surface. */\nexport interface OpenapiMergeResult {\n /** The merged living document, or null when the feature document has no paths to merge. */\n text: string | null;\n /** Labels of existing operations overwritten with different content. */\n modified: string[];\n /**\n * Labels of PATH-LEVEL keys (`parameters`, `servers`, `summary`, `x-*`)\n * overwritten with different content. They are not operations, so they were\n * excluded from the difference check entirely and overwritten in silence —\n * a delta restating a path with a shorter `parameters` list dropped shared\n * parameters from every operation under it, and the plan said nothing.\n */\n pathItemModified: string[];\n /** Labels of living operations deleted by `x-loam-remove: true` markers. */\n removed: string[];\n /**\n * Labels of operations the delta QUOTED — pinned, and equal to their own\n * baseline — so the merge left the living contract's own copy alone. Reported\n * rather than silent: \"your delta mentions this and I did not write it\" is\n * exactly the sentence whose absence made the revert invisible.\n */\n quoted: string[];\n /**\n * Labels of operations whose pin matches neither the delta's own content nor\n * the living one: edited here AND changed by somebody else in between.\n * `openapi.baseline-stale` refuses these at the gate; the merge still\n * overwrites, because reaching it at all means `--approve` said to.\n */\n baselineStale: string[];\n /** `<kind>/<name>` of living components overwritten with different content. */\n componentsModified: string[];\n /** Local refs reachable from merged content that resolve in neither document. */\n unresolved: Array<{ ref: string; from: string }>;\n}\n\nexport type OpenapiMergeSource = \"feature\" | \"living\";\n\n/** A document the merge planner cannot read. Commands map this domain error to their own envelope. */\nexport class OpenapiMergeError extends Error {\n override readonly name = \"OpenapiMergeError\";\n\n constructor(\n readonly source: OpenapiMergeSource,\n readonly service: string,\n detail: string,\n ) {\n super(`${source} openapi for ${service} is not valid YAML: ${detail}`);\n }\n}\n\n/**\n * Remove feature-only operation-removal markers before a feature contract is\n * used to create a brand-new living document. Normally coherence refuses this\n * shape because there cannot be a removal target; this guard also keeps\n * `--approve` from ever persisting the marker into living docs.\n */\nexport function stripOpenapiRemovalMarkers(featureText: string, service: string): string {\n const feature = parseDocument(featureText);\n if (feature.errors.length > 0) {\n throw new OpenapiMergeError(\"feature\", service, feature.errors[0]!.message);\n }\n let plain: unknown;\n try {\n plain = feature.toJS() ?? {};\n } catch (error) {\n throw new OpenapiMergeError(\"feature\", service, errorMessage(error));\n }\n // Shape from the RESOLVED tree, for the same reason the merge reads it there:\n // an aliased `paths` or path item is not a map node, and treating it as\n // \"nothing to strip\" is how a feature-only marker reached a living contract.\n const paths = plainChild(plain, \"paths\");\n if (!isRecord(paths)) return featureText;\n\n const cleaned: Record<string, unknown> = {};\n let stripped = false;\n for (const [path, item] of Object.entries(paths)) {\n const kept = withoutFeatureMarkers(item);\n if (!isDeepStrictEqual(kept, item)) stripped = true;\n // A path the strip emptied goes with its last operation: `\\/x: {}` is a\n // path the contract advertises and nothing answers.\n if (kept !== undefined) cleaned[path] = kept;\n }\n if (!stripped) return featureText;\n\n // Edit the AST in place when every node the strip touches IS a node — the\n // author's comments, key order and formatting are theirs to keep. An alias\n // cannot be edited in place (there is one shared value behind it, and\n // deleting a key through it would change every use), so those documents get\n // their `paths` rewritten from the resolved tree instead: losing formatting\n // is a cost, shipping the marker is a corruption.\n const editable =\n isMap(feature.get(\"paths\")) && Object.keys(paths).every((p) => isMap(feature.getIn([\"paths\", p])));\n if (!editable) {\n feature.setIn([\"paths\"], cleaned);\n } else {\n for (const [path, item] of Object.entries(paths)) {\n if (!isRecord(item)) continue;\n for (const [m, value] of Object.entries(item)) {\n // A loam marker at PATH level, deleted here for the same reason the\n // resolved-tree branch drops it: the two branches must agree exactly\n // about what a living contract may carry.\n if (FEATURE_ONLY_KEYS.has(m)) feature.deleteIn([\"paths\", path, m]);\n if (!HTTP_METHODS.has(m)) continue;\n if (isRemoval(value)) feature.deleteIn([\"paths\", path, m]);\n // The surviving operations keep their formatting but lose their pin:\n // the in-place branch must strip everything the resolved-tree branch\n // does, or the two disagree about what a living contract may carry.\n else if (operationBaselineOf(value) !== undefined) {\n feature.deleteIn([\"paths\", path, m, OPENAPI_BASELINE_KEY]);\n }\n }\n const remaining = feature.getIn([\"paths\", path]);\n if (isMap(remaining) && remaining.items.length === 0) feature.deleteIn([\"paths\", path]);\n }\n }\n try {\n return feature.toString();\n } catch (error) {\n throw new OpenapiMergeError(\"feature\", service, errorMessage(error));\n }\n}\n\n/**\n * Merge the feature's `paths` into the living OpenAPI structurally (YAML AST, not\n * text splicing). A feature document without paths is a successful no-op.\n * The merged operations' local component-ref closure rides along recursively;\n * external refs are left untouched and never gated.\n *\n * Every SHAPE question — is there a `paths` mapping, is this path item a\n * mapping, which methods does it hold — is answered from the RESOLVED plain\n * trees, never from the AST node. Asking the AST (`isMap(node)`) answers a\n * different question: an alias node is not a map even when it resolves to one.\n * `paths: *alias` therefore read as \"no paths to merge\" and the whole contract\n * delta was dropped with a successful exit, and an aliased path ITEM fell into\n * the wholesale-replace branch and deleted every living operation on that path\n * that the alias did not restate. Aliases are legal OpenAPI and the natural way\n * to write a delta that repeats a shape; nothing here may treat them as absent.\n * An alias that cannot be resolved at all is a document loam cannot read, and\n * says so — it is never \"nothing to merge\".\n */\nexport function mergeOpenapiPaths(\n livingText: string,\n featureText: string,\n service: string,\n): OpenapiMergeResult {\n const feature = parseDocument(featureText);\n if (feature.errors.length > 0) {\n throw new OpenapiMergeError(\"feature\", service, feature.errors[0]!.message);\n }\n let featPlain: unknown;\n try {\n // Resolve aliases once with the document's own anchor context. Calling an\n // individual AST node's toJSON() loses that context and can silently turn\n // an aliased operation or component into the wrong value.\n featPlain = feature.toJS() ?? {};\n } catch (error) {\n throw new OpenapiMergeError(\"feature\", service, errorMessage(error));\n }\n const featPathsPlain = plainChild(featPlain, \"paths\");\n if (featPathsPlain === undefined || featPathsPlain === null) {\n return noop();\n }\n if (!isRecord(featPathsPlain)) {\n throw new OpenapiMergeError(\"feature\", service, \"`paths` is not a mapping\");\n }\n const featPathEntries = Object.entries(featPathsPlain);\n if (featPathEntries.length === 0) return noop();\n\n const living = parseDocument(livingText);\n if (living.errors.length > 0) {\n throw new OpenapiMergeError(\"living\", service, living.errors[0]!.message);\n }\n let livingPlain: unknown;\n try {\n livingPlain = living.toJS() ?? {};\n } catch (error) {\n throw new OpenapiMergeError(\"living\", service, errorMessage(error));\n }\n const livingPathsPlain = plainChild(livingPlain, \"paths\");\n if (livingPathsPlain !== undefined && livingPathsPlain !== null && !isRecord(livingPathsPlain)) {\n throw new OpenapiMergeError(\"living\", service, \"`paths` is not a mapping\");\n }\n const modified: string[] = [];\n const pathItemModified: string[] = [];\n const removed: string[] = [];\n const quoted: string[] = [];\n const baselineStale: string[] = [];\n for (const [path, featItemPlain] of featPathEntries) {\n const existing = living.getIn([\"paths\", path]);\n const existingPlain = plainChild(livingPathsPlain, path);\n if (existing !== undefined && !isRecord(existingPlain)) {\n // The living contract holds this path as something other than a path\n // item. Replacing it wholesale is the one branch that may delete living\n // operations without naming them, so it is reserved for paths the living\n // contract does not have at all.\n throw new OpenapiMergeError(\"living\", service, `path '${path}' is not a mapping`);\n }\n if (existing !== undefined) {\n if (!isRecord(featItemPlain)) {\n throw new OpenapiMergeError(\"feature\", service, `path '${path}' is not a mapping`);\n }\n // EVERY branch publishes through withoutFeatureMarkers. This one used to\n // copy every key of the feature path item and guard only the HTTP\n // methods, so a path-level `x-loam-remove` landed in the living contract\n // with `modified: []` and no warning. A key the strip drops is never\n // written; the removal handling below still reads the RAW item, because\n // that is where the markers it acts on live.\n const publish = withoutFeatureMarkers(featItemPlain);\n const publishable = isRecord(publish) ? publish : {};\n for (const [m, afterPlain] of Object.entries(featItemPlain)) {\n const before = living.getIn([\"paths\", path, m]);\n const beforePlain = plainChild(existingPlain, m);\n if (HTTP_METHODS.has(m) && isRemoval(afterPlain)) {\n // Coherence validates the marker and gates absent/mismatched targets.\n // The merge remains defensive under --approve: never delete a\n // different operation merely because it occupies the requested slot.\n if (\n before !== undefined &&\n operationIdOf(beforePlain) !== undefined &&\n operationIdOf(beforePlain) === operationIdOf(afterPlain)\n ) {\n removed.push(opLabel(beforePlain, afterPlain, m, path));\n living.deleteIn([\"paths\", path, m]);\n }\n continue;\n }\n if (HTTP_METHODS.has(m)) {\n const verdict = classifyOperationBaseline(afterPlain, before === undefined ? undefined : beforePlain);\n // A QUOTE is not a merge input. The author wrote this operation down\n // because the document is complete, not because they changed it, so\n // the living contract keeps whatever it holds — including a change\n // that landed after this delta was written. This is mechanical, not a\n // judgement, so `--approve` does not turn it back on: overriding a\n // gate is a decision, reverting an operation nobody edited is a bug.\n if (verdict === \"quote\") {\n quoted.push(opLabel(beforePlain, afterPlain, m, path));\n continue;\n }\n if (verdict === \"stale\") baselineStale.push(opLabel(beforePlain, afterPlain, m, path));\n }\n if (!(m in publishable)) {\n // A feature-only marker at path level. It is never published, and\n // never reported as a path-item overwrite either — it is not a key of\n // the contract at all. The archive gate names it to the author.\n continue;\n }\n // The difference check covers EVERY key of the path item; only the\n // LABEL depends on whether the key is an HTTP method.\n if (before !== undefined && !isDeepStrictEqual(beforePlain, afterPlain)) {\n if (HTTP_METHODS.has(m)) modified.push(opLabel(beforePlain, afterPlain, m, path));\n else pathItemModified.push(`'${m}' (${path})`);\n }\n // Without the strip the living contract would grow a pin to a version\n // of itself, and the NEXT feature's baseline would hash it.\n living.setIn([\"paths\", path, m], publishable[m]);\n }\n // Removing the last method leaves `\\/x: {}` — a path the contract still\n // advertises and nothing answers. The same cleanup\n // stripOpenapiRemovalMarkers already does on the feature side.\n const remaining = living.getIn([\"paths\", path]);\n if (isMap(remaining) && remaining.items.length === 0) living.deleteIn([\"paths\", path]);\n continue;\n }\n if (!isRecord(featItemPlain)) {\n throw new OpenapiMergeError(\"feature\", service, `path '${path}' is not a mapping`);\n }\n const clean = withoutFeatureMarkers(featItemPlain);\n if (clean !== undefined) living.setIn([\"paths\", path], clean);\n }\n\n const componentsModified: string[] = [];\n const unresolved: OpenapiMergeResult[\"unresolved\"] = [];\n const visited = new Set<string>();\n const copies: Array<{ kind: string; name: string; value: unknown }> = [];\n\n const visitRef = (ref: string, from: string): void => {\n if (!ref.startsWith(\"#/\")) return;\n const match = /^#\\/components\\/([^/]+)\\/([^/]+)(?:\\/|$)/.exec(ref);\n if (!match) {\n if (!resolvePointer(featPlain, ref).found && !resolvePointer(livingPlain, ref).found) {\n unresolved.push({ ref, from });\n }\n return;\n }\n const kind = match[1]!;\n const name = match[2]!;\n const key = `${kind}/${name}`;\n if (visited.has(key)) return;\n visited.add(key);\n const inFeature = resolvePointer(featPlain, `#/components/${kind}/${name}`);\n if (inFeature.found) {\n copies.push({ kind, name, value: inFeature.value });\n walk(inFeature.value, `components/${key}`);\n return;\n }\n if (!resolvePointer(livingPlain, `#/components/${kind}/${name}`).found) {\n unresolved.push({ ref, from });\n }\n };\n\n const walk = (node: unknown, from: string): void => {\n for (const ref of collectRefs(node)) visitRef(ref, from);\n };\n\n for (const [path, featItemPlain] of featPathEntries) {\n walk(withoutFeatureMarkers(featItemPlain), `paths ${path}`);\n }\n\n for (const { kind, name, value } of copies) {\n const existing = living.getIn([\"components\", kind, name]);\n if (existing !== undefined) {\n const existingPlain = resolvePointer(livingPlain, `#/components/${kind}/${name}`);\n if (existingPlain.found && isDeepStrictEqual(existingPlain.value, value)) continue;\n componentsModified.push(`${kind}/${name}`);\n }\n living.setIn([\"components\", kind, name], value);\n }\n\n let text: string;\n try {\n text = living.toString();\n } catch (error) {\n throw new OpenapiMergeError(\"living\", service, errorMessage(error));\n }\n return { text, modified, pathItemModified, removed, quoted, baselineStale, componentsModified, unresolved };\n}\n\n/** The successful \"the feature document has nothing to merge\" answer. */\nfunction noop(): OpenapiMergeResult {\n return {\n text: null,\n modified: [],\n pathItemModified: [],\n removed: [],\n quoted: [],\n baselineStale: [],\n componentsModified: [],\n unresolved: [],\n };\n}\n\n/** A resolved plain tree that can hold OpenAPI keys — an object, not an array. */\nfunction isRecord(node: unknown): node is Record<string, unknown> {\n return node !== null && typeof node === \"object\" && !Array.isArray(node);\n}\n\n/** Every `$ref` string value anywhere in a plain JS tree, in document order. */\nexport function collectRefs(node: unknown): string[] {\n const out: string[] = [];\n const walk = (current: unknown): void => {\n if (Array.isArray(current)) {\n for (const value of current) walk(value);\n return;\n }\n if (current !== null && typeof current === \"object\") {\n for (const [key, value] of Object.entries(current)) {\n if (key === \"$ref\" && typeof value === \"string\") out.push(value);\n else walk(value);\n }\n }\n };\n walk(node);\n return out;\n}\n\n/** Resolve a local JSON pointer (`#/a/b~1c`) against a plain JS tree. */\nexport function resolvePointer(root: unknown, ref: string): { found: boolean; value: unknown } {\n let current: unknown = root;\n for (const raw of ref.slice(2).split(\"/\")) {\n const segment = raw.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n if (Array.isArray(current)) {\n const index = Number(segment);\n if (!Number.isInteger(index) || index < 0 || index >= current.length) {\n return { found: false, value: undefined };\n }\n current = current[index];\n } else if (current !== null && typeof current === \"object\" && segment in (current as Record<string, unknown>)) {\n current = (current as Record<string, unknown>)[segment];\n } else {\n return { found: false, value: undefined };\n }\n }\n return { found: true, value: current };\n}\n\n/** Name an overwritten operation by its operationId (feature's, else living's), or path+method. */\nexport function opLabel(before: unknown, after: unknown, method: string, path: string): string {\n const operation = operationIdOf(after) ?? operationIdOf(before);\n return operation !== undefined ? `'${operation}' (${method} ${path})` : `${method} ${path}`;\n}\n\n/** Is this operation node a feature-only explicit removal marker? */\nfunction isRemoval(node: unknown): boolean {\n return node !== null &&\n typeof node === \"object\" &&\n (node as Record<string, unknown>)[\"x-loam-remove\"] === true;\n}\n\n/** Read an operationId from a YAML or plain operation node. */\nfunction operationIdOf(node: unknown): string | undefined {\n const value = isMap(node)\n ? node.get(\"operationId\")\n : node !== null && typeof node === \"object\"\n ? (node as Record<string, unknown>)[\"operationId\"]\n : undefined;\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\n/**\n * Every key that is an instruction to loam rather than part of the contract.\n * At PATH level neither has any meaning — `x-loam-remove` addresses one\n * operation and `x-loam-based-on` pins one — so a path item carrying either is\n * an authoring mistake, and one that must not be published whatever else is\n * done about it.\n */\nconst FEATURE_ONLY_KEYS = new Set([\"x-loam-remove\", OPENAPI_BASELINE_KEY]);\n\n/**\n * Copy a path item without anything that belongs to the FEATURE rather than to\n * the contract: removal-marker operations, the `x-loam-based-on` pins on the\n * operations that survive, and a loam marker written at PATH level. All three\n * are instructions to loam, and a living contract that carries any of them is\n * lying to the next reader — and, for the pin, to the next feature's baseline.\n *\n * The path-level case used to fall through: the filter guarded only HTTP\n * methods, so `x-loam-remove: true` beside the methods was copied verbatim into\n * the fleet's living contract, where it also kept the empty-path cleanup from\n * ever seeing an empty path.\n */\nfunction withoutFeatureMarkers(node: unknown): unknown {\n if (node === null || typeof node !== \"object\" || Array.isArray(node)) return node;\n const entries = Object.entries(node as Record<string, unknown>)\n .filter(([key, value]) => !FEATURE_ONLY_KEYS.has(key) && !(HTTP_METHODS.has(key) && isRemoval(value)))\n .map(([key, value]) => [key, HTTP_METHODS.has(key) ? withoutOperationBaseline(value) : value] as const);\n return entries.length === 0 ? undefined : Object.fromEntries(entries);\n}\n\nfunction plainChild(parent: unknown, key: string): unknown {\n return parent !== null && typeof parent === \"object\"\n ? (parent as Record<string, unknown>)[key]\n : undefined;\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"]}
1
+ {"version":3,"file":"openapi-merge.js","sourceRoot":"","sources":["../../src/core/openapi-merge.ts"],"names":[],"mappings":"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","sourcesContent":["import { isDeepStrictEqual } from \"node:util\";\nimport { isMap, parseDocument } from \"yaml\";\nimport {\n HTTP_METHODS,\n OPENAPI_BASELINE_KEY,\n classifyBaselineDigests,\n operationBaselineOf,\n operationDigest,\n withoutOperationBaseline,\n} from \"./openapi.js\";\n// Every `isRecord` below asks one question of a resolved plain tree: is this a\n// node that can hold OpenAPI keys — an object, not an array?\nimport { isRecord } from \"./records.js\";\n\n/**\n * What a feature's operation IS, relative to the living contract — the one\n * judgement that tells a QUOTE from an EDIT.\n *\n * A feature's openapi.yaml is a complete document, so most of what it spells is\n * quotation: the author restated the living contract around the slot they were\n * changing. Without a pin the merge cannot tell the two apart and upserts both,\n * which is how a feature that only meant to touch `cancelOrder` silently\n * reverted another team's landed change to `refundOrder` — no overlap between\n * the features required, just two deltas over one service.\n */\nexport type OperationBaselineVerdict = ReturnType<typeof classifyBaselineDigests>;\n\n/**\n * Classify one feature operation against its living counterpart, from the raw\n * YAML trees the merge works in. The rule itself lives in core/openapi.ts, so\n * the gate and the merge cannot drift apart about which operations get written.\n *\n * A malformed pin matches no digest and lands in `stale`, which merges rather\n * than skips. Deliberate: `openapi.baseline-invalid` refuses it at the gate, and\n * a value nobody can evaluate must never be the reason a merge quietly drops an\n * operation.\n */\nexport function classifyOperationBaseline(featureOp: unknown, livingOp: unknown): OperationBaselineVerdict {\n return classifyBaselineDigests(\n operationBaselineOf(featureOp),\n operationDigest(featureOp),\n livingOp === undefined ? undefined : operationDigest(livingOp),\n );\n}\n\n/** What happened to one operation's `x-loam-based-on`. */\nexport interface OperationPin {\n path: string;\n method: string;\n /** Empty when the operation declares none — the slot is still the identity. */\n operationId: string;\n status:\n /** It had no pin and now has one. */\n | \"pinned\"\n /** It named an older living version; it now names the current one. */\n | \"repinned\"\n /** It already named the current living version — no write. */\n | \"unchanged\"\n /** The living contract has no operation at this slot: this feature is adding it. */\n | \"unresolved\"\n /** Written as a YAML alias — one shared value backs every use, so loam will not stamp through it. */\n | \"unwritable\";\n from: string | null;\n to: string | null;\n}\n\nexport interface OpenapiPinPlan {\n /** The rewritten contract, or null when no pin changes. */\n text: string | null;\n pins: OperationPin[];\n}\n\n/**\n * Pin every operation in a feature contract against the living one — what\n * `loam rebase` writes on this axis.\n *\n * The pin is always `operationDigest` of the LIVING operation, never of the\n * delta's own, and that single rule produces both merge verdicts by itself: an\n * operation the author only QUOTED is byte-equal to living, so its pin equals\n * its own content and `classifyOperationBaseline` calls it a quote; one the\n * author EDITED differs from its pin, so the merge writes it. Nothing has to\n * guess at intent — the document already records it, and the pin makes it\n * legible to the merge.\n *\n * Slot-keyed (path + method), exactly as the merge upserts. An operationId that\n * moved to another slot is a new slot with nothing living behind it, which is\n * `unresolved` and correct: there is no living version of an operation at a\n * path the contract does not serve yet.\n */\nexport function pinOpenapiOperations(featureText: string, livingText: string, service: string): OpenapiPinPlan {\n const doc = parseDocument(featureText);\n if (doc.errors.length > 0) throw new OpenapiMergeError(\"feature\", service, doc.errors[0]!.message);\n let featPlain: unknown;\n let livingPlain: unknown;\n try {\n featPlain = doc.toJS() ?? {};\n } catch (error) {\n throw new OpenapiMergeError(\"feature\", service, errorMessage(error));\n }\n try {\n const parsed = parseDocument(livingText);\n if (parsed.errors.length > 0) throw new OpenapiMergeError(\"living\", service, parsed.errors[0]!.message);\n livingPlain = parsed.toJS() ?? {};\n } catch (error) {\n if (error instanceof OpenapiMergeError) throw error;\n throw new OpenapiMergeError(\"living\", service, errorMessage(error));\n }\n\n const featPaths = plainChild(featPlain, \"paths\");\n if (!isRecord(featPaths)) return { text: null, pins: [] };\n const livingPaths = plainChild(livingPlain, \"paths\");\n\n const pins: OperationPin[] = [];\n let edited = false;\n for (const [path, item] of Object.entries(featPaths)) {\n if (!isRecord(item)) continue;\n for (const [method, op] of Object.entries(item)) {\n if (!HTTP_METHODS.has(method) || !isRecord(op)) continue;\n // A removal marker asserts a slot rather than restating an operation, so\n // it has nothing to be based on; `openapi.remove-target-mismatch` is what\n // guards it against a living slot that moved under it.\n if (isRemoval(op)) continue;\n\n const id = typeof op[\"operationId\"] === \"string\" ? op[\"operationId\"] : \"\";\n const at = { path, method, operationId: id };\n const from = operationBaselineOf(op) ?? null;\n const livingItem = plainChild(livingPaths, path);\n const livingOp = isRecord(livingItem) ? livingItem[method] : undefined;\n if (!isRecord(livingOp)) {\n pins.push({ ...at, status: \"unresolved\", from, to: null });\n continue;\n }\n const digest = operationDigest(livingOp);\n if (from === digest) {\n pins.push({ ...at, status: \"unchanged\", from: digest, to: digest });\n continue;\n }\n // One shared value backs every use of an anchor, so `setIn` through an\n // alias would pin every other use of it too — the same reason\n // stripOpenapiRemovalMarkers refuses to edit aliases in place.\n if (!isMap(doc.getIn([\"paths\", path, method]))) {\n pins.push({ ...at, status: \"unwritable\", from, to: null });\n continue;\n }\n pins.push({ ...at, status: from === null ? \"pinned\" : \"repinned\", from, to: digest });\n doc.setIn([\"paths\", path, method, OPENAPI_BASELINE_KEY], digest);\n edited = true;\n }\n }\n\n if (!edited) return { text: null, pins };\n try {\n return { text: doc.toString(), pins };\n } catch (error) {\n throw new OpenapiMergeError(\"feature\", service, errorMessage(error));\n }\n}\n\n/** What an OpenAPI path merge computed, including every condition the caller must surface. */\nexport interface OpenapiMergeResult {\n /** The merged living document, or null when the feature document has no paths to merge. */\n text: string | null;\n /** Labels of existing operations overwritten with different content. */\n modified: string[];\n /**\n * Labels of PATH-LEVEL keys (`parameters`, `servers`, `summary`, `x-*`)\n * overwritten with different content. They are not operations, so they were\n * excluded from the difference check entirely and overwritten in silence —\n * a delta restating a path with a shorter `parameters` list dropped shared\n * parameters from every operation under it, and the plan said nothing.\n */\n pathItemModified: string[];\n /** Labels of living operations deleted by `x-loam-remove: true` markers. */\n removed: string[];\n /**\n * Labels of operations the delta QUOTED — pinned, and equal to their own\n * baseline — so the merge left the living contract's own copy alone. Reported\n * rather than silent: \"your delta mentions this and I did not write it\" is\n * exactly the sentence whose absence made the revert invisible.\n */\n quoted: string[];\n /**\n * Labels of operations whose pin matches neither the delta's own content nor\n * the living one: edited here AND changed by somebody else in between.\n * `openapi.baseline-stale` refuses these at the gate; the merge still\n * overwrites, because reaching it at all means `--approve` said to.\n */\n baselineStale: string[];\n /** `<kind>/<name>` of living components overwritten with different content. */\n componentsModified: string[];\n /** Local refs reachable from merged content that resolve in neither document. */\n unresolved: Array<{ ref: string; from: string }>;\n}\n\nexport type OpenapiMergeSource = \"feature\" | \"living\";\n\n/** A document the merge planner cannot read. Commands map this domain error to their own envelope. */\nexport class OpenapiMergeError extends Error {\n override readonly name = \"OpenapiMergeError\";\n\n constructor(\n readonly source: OpenapiMergeSource,\n readonly service: string,\n detail: string,\n ) {\n super(`${source} openapi for ${service} is not valid YAML: ${detail}`);\n }\n}\n\n/**\n * Remove feature-only operation-removal markers before a feature contract is\n * used to create a brand-new living document. Normally coherence refuses this\n * shape because there cannot be a removal target; this guard also keeps\n * `--approve` from ever persisting the marker into living docs.\n */\nexport function stripOpenapiRemovalMarkers(featureText: string, service: string): string {\n const feature = parseDocument(featureText);\n if (feature.errors.length > 0) {\n throw new OpenapiMergeError(\"feature\", service, feature.errors[0]!.message);\n }\n let plain: unknown;\n try {\n plain = feature.toJS() ?? {};\n } catch (error) {\n throw new OpenapiMergeError(\"feature\", service, errorMessage(error));\n }\n // Shape from the RESOLVED tree, for the same reason the merge reads it there:\n // an aliased `paths` or path item is not a map node, and treating it as\n // \"nothing to strip\" is how a feature-only marker reached a living contract.\n const paths = plainChild(plain, \"paths\");\n if (!isRecord(paths)) return featureText;\n\n const cleaned: Record<string, unknown> = {};\n let stripped = false;\n for (const [path, item] of Object.entries(paths)) {\n const kept = withoutFeatureMarkers(item);\n if (!isDeepStrictEqual(kept, item)) stripped = true;\n // A path the strip emptied goes with its last operation: `\\/x: {}` is a\n // path the contract advertises and nothing answers.\n if (kept !== undefined) cleaned[path] = kept;\n }\n if (!stripped) return featureText;\n\n // Edit the AST in place when every node the strip touches IS a node — the\n // author's comments, key order and formatting are theirs to keep. An alias\n // cannot be edited in place (there is one shared value behind it, and\n // deleting a key through it would change every use), so those documents get\n // their `paths` rewritten from the resolved tree instead: losing formatting\n // is a cost, shipping the marker is a corruption.\n //\n // The test has to reach the OPERATION nodes too, not just the path items. The\n // in-place branch strips a surviving operation's pin through\n // `[\"paths\", p, m, x-loam-based-on]`, and an aliased operation is exactly one\n // level below where the check used to look — so `deleteIn` walked into a node\n // that is not a collection and threw out of archive as `internal`, on a\n // document whose only fault was writing one operation twice.\n //\n // What disqualifies a method is \"there is something here to strip and I cannot\n // reach it\", not \"this is not a map node\". Only a resolved MAPPING can be a\n // removal marker or carry a pin, so a method spelled as a scalar — `get: ~`,\n // or a string — is nothing the in-place branch would have touched; demanding a\n // map node of it too demoted whole documents to the rewrite branch and cost\n // their author every comment, anchor and hand-chosen key order inside `paths`,\n // in the bytes archive installs as the living contract. What is left is the\n // real hazard: a mapping the AST does not hand back as a map. An alias is the\n // one node kind that resolves to a mapping without being one, and a method\n // whose KEY is an alias is not findable by name at all, so `deleteIn` reports\n // nothing deleted and the marker ships in silence. Both answer `undefined` or\n // a non-map here, and both belong on the safe branch.\n const editable =\n isMap(feature.get(\"paths\")) &&\n Object.entries(paths).every(\n ([p, item]) =>\n isMap(feature.getIn([\"paths\", p])) &&\n (!isRecord(item) ||\n Object.entries(item).every(\n ([m, op]) => !HTTP_METHODS.has(m) || !isRecord(op) || isMap(feature.getIn([\"paths\", p, m])),\n )),\n );\n if (!editable) {\n feature.setIn([\"paths\"], cleaned);\n } else {\n for (const [path, item] of Object.entries(paths)) {\n if (!isRecord(item)) continue;\n for (const [m, value] of Object.entries(item)) {\n // A loam marker at PATH level, deleted here for the same reason the\n // resolved-tree branch drops it: the two branches must agree exactly\n // about what a living contract may carry.\n if (FEATURE_ONLY_KEYS.has(m)) feature.deleteIn([\"paths\", path, m]);\n if (!HTTP_METHODS.has(m)) continue;\n if (isRemoval(value)) feature.deleteIn([\"paths\", path, m]);\n // The surviving operations keep their formatting but lose their pin:\n // the in-place branch must strip everything the resolved-tree branch\n // does, or the two disagree about what a living contract may carry.\n else if (operationBaselineOf(value) !== undefined) {\n feature.deleteIn([\"paths\", path, m, OPENAPI_BASELINE_KEY]);\n }\n }\n const remaining = feature.getIn([\"paths\", path]);\n if (isMap(remaining) && remaining.items.length === 0) feature.deleteIn([\"paths\", path]);\n }\n }\n try {\n return feature.toString();\n } catch (error) {\n throw new OpenapiMergeError(\"feature\", service, errorMessage(error));\n }\n}\n\n/**\n * Merge the feature's `paths` into the living OpenAPI structurally (YAML AST, not\n * text splicing). A feature document without paths is a successful no-op.\n * The merged operations' local component-ref closure rides along recursively;\n * external refs are left untouched and never gated.\n *\n * Every SHAPE question — is there a `paths` mapping, is this path item a\n * mapping, which methods does it hold — is answered from the RESOLVED plain\n * trees, never from the AST node. Asking the AST (`isMap(node)`) answers a\n * different question: an alias node is not a map even when it resolves to one.\n * `paths: *alias` therefore read as \"no paths to merge\" and the whole contract\n * delta was dropped with a successful exit, and an aliased path ITEM fell into\n * the wholesale-replace branch and deleted every living operation on that path\n * that the alias did not restate. Aliases are legal OpenAPI and the natural way\n * to write a delta that repeats a shape; nothing here may treat them as absent.\n * An alias that cannot be resolved at all is a document loam cannot read, and\n * says so — it is never \"nothing to merge\".\n */\nexport function mergeOpenapiPaths(\n livingText: string,\n featureText: string,\n service: string,\n): OpenapiMergeResult {\n const feature = parseDocument(featureText);\n if (feature.errors.length > 0) {\n throw new OpenapiMergeError(\"feature\", service, feature.errors[0]!.message);\n }\n let featPlain: unknown;\n try {\n // Resolve aliases once with the document's own anchor context. Calling an\n // individual AST node's toJSON() loses that context and can silently turn\n // an aliased operation or component into the wrong value.\n featPlain = feature.toJS() ?? {};\n } catch (error) {\n throw new OpenapiMergeError(\"feature\", service, errorMessage(error));\n }\n const featPathsPlain = plainChild(featPlain, \"paths\");\n if (featPathsPlain === undefined || featPathsPlain === null) {\n return noop();\n }\n if (!isRecord(featPathsPlain)) {\n throw new OpenapiMergeError(\"feature\", service, \"`paths` is not a mapping\");\n }\n const featPathEntries = Object.entries(featPathsPlain);\n if (featPathEntries.length === 0) return noop();\n\n const living = parseDocument(livingText);\n if (living.errors.length > 0) {\n throw new OpenapiMergeError(\"living\", service, living.errors[0]!.message);\n }\n let livingPlain: unknown;\n try {\n livingPlain = living.toJS() ?? {};\n } catch (error) {\n throw new OpenapiMergeError(\"living\", service, errorMessage(error));\n }\n const livingPathsPlain = plainChild(livingPlain, \"paths\");\n if (livingPathsPlain !== undefined && livingPathsPlain !== null && !isRecord(livingPathsPlain)) {\n throw new OpenapiMergeError(\"living\", service, \"`paths` is not a mapping\");\n }\n const modified: string[] = [];\n const pathItemModified: string[] = [];\n const removed: string[] = [];\n const quoted: string[] = [];\n const baselineStale: string[] = [];\n for (const [path, featItemPlain] of featPathEntries) {\n const existing = living.getIn([\"paths\", path]);\n const existingPlain = plainChild(livingPathsPlain, path);\n if (existing !== undefined && !isRecord(existingPlain)) {\n // The living contract holds this path as something other than a path\n // item. Replacing it wholesale is the one branch that may delete living\n // operations without naming them, so it is reserved for paths the living\n // contract does not have at all.\n throw new OpenapiMergeError(\"living\", service, `path '${path}' is not a mapping`);\n }\n if (existing !== undefined) {\n if (!isRecord(featItemPlain)) {\n throw new OpenapiMergeError(\"feature\", service, `path '${path}' is not a mapping`);\n }\n // EVERY branch publishes through withoutFeatureMarkers. This one used to\n // copy every key of the feature path item and guard only the HTTP\n // methods, so a path-level `x-loam-remove` landed in the living contract\n // with `modified: []` and no warning. A key the strip drops is never\n // written; the removal handling below still reads the RAW item, because\n // that is where the markers it acts on live.\n const publish = withoutFeatureMarkers(featItemPlain);\n const publishable = isRecord(publish) ? publish : {};\n for (const [m, afterPlain] of Object.entries(featItemPlain)) {\n const before = living.getIn([\"paths\", path, m]);\n const beforePlain = plainChild(existingPlain, m);\n if (HTTP_METHODS.has(m) && isRemoval(afterPlain)) {\n // Coherence validates the marker and gates absent/mismatched targets.\n // The merge remains defensive under --approve: never delete a\n // different operation merely because it occupies the requested slot.\n if (\n before !== undefined &&\n operationIdOf(beforePlain) !== undefined &&\n operationIdOf(beforePlain) === operationIdOf(afterPlain)\n ) {\n removed.push(opLabel(beforePlain, afterPlain, m, path));\n living.deleteIn([\"paths\", path, m]);\n }\n continue;\n }\n if (HTTP_METHODS.has(m)) {\n const verdict = classifyOperationBaseline(afterPlain, before === undefined ? undefined : beforePlain);\n // A QUOTE is not a merge input. The author wrote this operation down\n // because the document is complete, not because they changed it, so\n // the living contract keeps whatever it holds — including a change\n // that landed after this delta was written. This is mechanical, not a\n // judgement, so `--approve` does not turn it back on: overriding a\n // gate is a decision, reverting an operation nobody edited is a bug.\n if (verdict === \"quote\") {\n quoted.push(opLabel(beforePlain, afterPlain, m, path));\n continue;\n }\n if (verdict === \"stale\") baselineStale.push(opLabel(beforePlain, afterPlain, m, path));\n }\n if (!(m in publishable)) {\n // A feature-only marker at path level. It is never published, and\n // never reported as a path-item overwrite either — it is not a key of\n // the contract at all. The archive gate names it to the author.\n continue;\n }\n // The difference check covers EVERY key of the path item; only the\n // LABEL depends on whether the key is an HTTP method.\n if (before !== undefined && !isDeepStrictEqual(beforePlain, afterPlain)) {\n if (HTTP_METHODS.has(m)) modified.push(opLabel(beforePlain, afterPlain, m, path));\n else pathItemModified.push(`'${m}' (${path})`);\n }\n // Every shape question above was answered from the RESOLVED tree, which\n // is exactly what makes an aliased path item read as an ordinary\n // mapping. The WRITE cannot be that forgiving: `setIn` walks the AST,\n // and one shared value backs every use of an anchor, so an operation\n // merged through the alias would land in every other path that\n // references it. There is no way to write into one use of an anchor and\n // not the rest, so this is a refusal rather than a fallback — and it is\n // asked here, at the write, because reading is safe and writing is not.\n if (!isMap(living.getIn([\"paths\", path]))) {\n throw new OpenapiMergeError(\n \"living\",\n service,\n `path '${path}' is written as a YAML alias — one shared value backs every use, so merging into it would rewrite every other path that aliases it. Expand it before archiving.`,\n );\n }\n // Without the strip the living contract would grow a pin to a version\n // of itself, and the NEXT feature's baseline would hash it.\n living.setIn([\"paths\", path, m], publishable[m]);\n }\n // Removing the last method leaves `\\/x: {}` — a path the contract still\n // advertises and nothing answers. The same cleanup\n // stripOpenapiRemovalMarkers already does on the feature side.\n const remaining = living.getIn([\"paths\", path]);\n if (isMap(remaining) && remaining.items.length === 0) living.deleteIn([\"paths\", path]);\n continue;\n }\n if (!isRecord(featItemPlain)) {\n throw new OpenapiMergeError(\"feature\", service, `path '${path}' is not a mapping`);\n }\n const clean = withoutFeatureMarkers(featItemPlain);\n if (clean !== undefined) living.setIn([\"paths\", path], clean);\n }\n\n const componentsModified: string[] = [];\n const unresolved: OpenapiMergeResult[\"unresolved\"] = [];\n const visited = new Set<string>();\n const copies: Array<{ kind: string; name: string; value: unknown }> = [];\n\n const visitRef = (ref: string, from: string): void => {\n if (!ref.startsWith(\"#/\")) return;\n const match = /^#\\/components\\/([^/]+)\\/([^/]+)(?:\\/|$)/.exec(ref);\n if (!match) {\n if (!resolvePointer(featPlain, ref).found && !resolvePointer(livingPlain, ref).found) {\n unresolved.push({ ref, from });\n }\n return;\n }\n const kind = match[1]!;\n const name = match[2]!;\n const key = `${kind}/${name}`;\n if (visited.has(key)) return;\n visited.add(key);\n const inFeature = resolvePointer(featPlain, `#/components/${kind}/${name}`);\n if (inFeature.found) {\n copies.push({ kind, name, value: inFeature.value });\n walk(inFeature.value, `components/${key}`);\n return;\n }\n if (!resolvePointer(livingPlain, `#/components/${kind}/${name}`).found) {\n unresolved.push({ ref, from });\n }\n };\n\n const walk = (node: unknown, from: string): void => {\n for (const ref of collectRefs(node)) visitRef(ref, from);\n };\n\n for (const [path, featItemPlain] of featPathEntries) {\n walk(withoutFeatureMarkers(featItemPlain), `paths ${path}`);\n }\n\n for (const { kind, name, value } of copies) {\n const existing = living.getIn([\"components\", kind, name]);\n if (existing !== undefined) {\n const existingPlain = resolvePointer(livingPlain, `#/components/${kind}/${name}`);\n if (existingPlain.found && isDeepStrictEqual(existingPlain.value, value)) continue;\n componentsModified.push(`${kind}/${name}`);\n }\n living.setIn([\"components\", kind, name], value);\n }\n\n let text: string;\n try {\n text = living.toString();\n } catch (error) {\n throw new OpenapiMergeError(\"living\", service, errorMessage(error));\n }\n return { text, modified, pathItemModified, removed, quoted, baselineStale, componentsModified, unresolved };\n}\n\n/** The successful \"the feature document has nothing to merge\" answer. */\nfunction noop(): OpenapiMergeResult {\n return {\n text: null,\n modified: [],\n pathItemModified: [],\n removed: [],\n quoted: [],\n baselineStale: [],\n componentsModified: [],\n unresolved: [],\n };\n}\n\n/** Every `$ref` string value anywhere in a plain JS tree, in document order. */\nexport function collectRefs(node: unknown): string[] {\n const out: string[] = [];\n const walk = (current: unknown): void => {\n if (Array.isArray(current)) {\n for (const value of current) walk(value);\n return;\n }\n if (current !== null && typeof current === \"object\") {\n for (const [key, value] of Object.entries(current)) {\n if (key === \"$ref\" && typeof value === \"string\") out.push(value);\n else walk(value);\n }\n }\n };\n walk(node);\n return out;\n}\n\n/** Resolve a local JSON pointer (`#/a/b~1c`) against a plain JS tree. */\nexport function resolvePointer(root: unknown, ref: string): { found: boolean; value: unknown } {\n let current: unknown = root;\n for (const raw of ref.slice(2).split(\"/\")) {\n const segment = raw.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n if (Array.isArray(current)) {\n const index = Number(segment);\n if (!Number.isInteger(index) || index < 0 || index >= current.length) {\n return { found: false, value: undefined };\n }\n current = current[index];\n } else if (current !== null && typeof current === \"object\" && segment in (current as Record<string, unknown>)) {\n current = (current as Record<string, unknown>)[segment];\n } else {\n return { found: false, value: undefined };\n }\n }\n return { found: true, value: current };\n}\n\n/** Name an overwritten operation by its operationId (feature's, else living's), or path+method. */\nfunction opLabel(before: unknown, after: unknown, method: string, path: string): string {\n const operation = operationIdOf(after) ?? operationIdOf(before);\n return operation !== undefined ? `'${operation}' (${method} ${path})` : `${method} ${path}`;\n}\n\n/** Is this operation node a feature-only explicit removal marker? */\nfunction isRemoval(node: unknown): boolean {\n return node !== null &&\n typeof node === \"object\" &&\n (node as Record<string, unknown>)[\"x-loam-remove\"] === true;\n}\n\n/** Read an operationId from a YAML or plain operation node. */\nfunction operationIdOf(node: unknown): string | undefined {\n const value = isMap(node)\n ? node.get(\"operationId\")\n : node !== null && typeof node === \"object\"\n ? (node as Record<string, unknown>)[\"operationId\"]\n : undefined;\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\n/**\n * Every key that is an instruction to loam rather than part of the contract.\n * At PATH level neither has any meaning — `x-loam-remove` addresses one\n * operation and `x-loam-based-on` pins one — so a path item carrying either is\n * an authoring mistake, and one that must not be published whatever else is\n * done about it.\n */\nconst FEATURE_ONLY_KEYS = new Set([\"x-loam-remove\", OPENAPI_BASELINE_KEY]);\n\n/**\n * Copy a path item without anything that belongs to the FEATURE rather than to\n * the contract: removal-marker operations, the `x-loam-based-on` pins on the\n * operations that survive, and a loam marker written at PATH level. All three\n * are instructions to loam, and a living contract that carries any of them is\n * lying to the next reader — and, for the pin, to the next feature's baseline.\n *\n * The path-level case used to fall through: the filter guarded only HTTP\n * methods, so `x-loam-remove: true` beside the methods was copied verbatim into\n * the fleet's living contract, where it also kept the empty-path cleanup from\n * ever seeing an empty path.\n */\nfunction withoutFeatureMarkers(node: unknown): unknown {\n if (node === null || typeof node !== \"object\" || Array.isArray(node)) return node;\n const entries = Object.entries(node as Record<string, unknown>)\n .filter(([key, value]) => !FEATURE_ONLY_KEYS.has(key) && !(HTTP_METHODS.has(key) && isRemoval(value)))\n .map(([key, value]) => [key, HTTP_METHODS.has(key) ? withoutOperationBaseline(value) : value] as const);\n return entries.length === 0 ? undefined : Object.fromEntries(entries);\n}\n\nfunction plainChild(parent: unknown, key: string): unknown {\n return parent !== null && typeof parent === \"object\"\n ? (parent as Record<string, unknown>)[key]\n : undefined;\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"]}
@@ -224,13 +224,25 @@ export async function operationIds(openapiPath, context) {
224
224
  * depending on which path sorted first in the YAML, and the requirement
225
225
  * governing it was reported undefined (`spec-api.op-undefined`) on exactly one
226
226
  * of the two spellings of the same change.
227
+ *
228
+ * A context is threaded into the READS and never asked for the answer. It used
229
+ * to own a second copy of this function, and that copy interleaved removals
230
+ * with upserts — the exact bug the paragraph above describes as fixed. So the
231
+ * commands that pass a context (`validate`, `status`) and the one that does not
232
+ * (`archive`) disagreed about whether a relocated operation still existed, and
233
+ * `spec-api.op-undefined` — an error that gates archive — fired or not
234
+ * depending on which path key the author happened to type first. Memoising is
235
+ * the context's job; the ordering rule is this function's, and there is one of
236
+ * it.
227
237
  */
228
238
  export async function serviceOperationIds(docsDir, service, featureDir, context) {
229
- if (context !== undefined)
230
- return context.serviceOperationIds(docsDir, service, featureDir);
231
- const ids = new Set((await operations(servicePaths(docsDir, service).openapi)).filter((op) => !op.remove).map((op) => op.id));
232
- if (featureDir) {
233
- const featOps = await operations(featureSpecPaths(featureDir, service).openapi);
239
+ const ids = new Set((await operations(servicePaths(docsDir, service).openapi, context)).filter((op) => !op.remove).map((op) => op.id));
240
+ // Absence is `undefined`, the shape the optional parameter declares. The
241
+ // truthiness test also read "" as "no feature", which is a caller mistake
242
+ // worth surfacing rather than answering as though the feature carried no
243
+ // contract delta.
244
+ if (featureDir !== undefined) {
245
+ const featOps = await operations(featureSpecPaths(featureDir, service).openapi, context);
234
246
  for (const op of featOps)
235
247
  if (op.remove)
236
248
  ids.delete(op.id);
@@ -1 +1 @@
1
- {"version":3,"file":"openapi.js","sourceRoot":"","sources":["../../src/core/openapi.ts"],"names":[],"mappings":"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","sourcesContent":["import { isUtf8 } from \"node:buffer\";\nimport { createHash } from \"node:crypto\";\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { parse } from \"yaml\";\nimport type { FleetContext } from \"./fleet-context.js\";\nimport { featureSpecPaths, servicePaths } from \"./repo.js\";\n\n/**\n * The path-item keys that hold operations. A path item also carries `summary`,\n * `parameters`, `servers` and vendor `x-*` extensions — an object-valued one with\n * an `operationId` inside (x-legacy and friends) is not an operation, and a\n * phantom id from it would make a broken contract look \"available\" to the\n * op-exists checks. Shared with archive's merge, which asks the same question\n * of the same keys.\n */\nexport const HTTP_METHODS = new Set([\"get\", \"put\", \"post\", \"delete\", \"options\", \"head\", \"patch\", \"trace\"]);\n\n/**\n * The baseline marker: which living version of this operation a FEATURE delta\n * was written against. Same idea as a requirement's `Based-On:` (core/spec.ts),\n * in the vendor-extension shape this axis already uses for `x-loam-remove`.\n *\n * This axis needs it MORE than the requirement axis does, because a feature's\n * openapi.yaml is a COMPLETE document rather than a patch: authors restate the\n * living contract around the slot they are changing, and the merge upserts every\n * operation the document spells. So a feature that never meant to touch\n * `refundOrder` — it merely quoted it — pushes its authoring-time copy back over\n * whatever landed in between, reverting another team's shipped change without\n * either feature overlapping the other at all. The pin is what lets the merge\n * tell a QUOTE from an EDIT: a quote equals its own baseline, and is skipped.\n */\nexport const OPENAPI_BASELINE_KEY = \"x-loam-based-on\";\n\n/** How much of the sha256 an `x-loam-based-on` carries — the length every loam digest uses. */\nexport const OPERATION_DIGEST_LENGTH = 16;\n\n/** The value shape of an `x-loam-based-on`, exactly as `operationDigest` writes it. */\nexport const OPERATION_DIGEST_RE = new RegExp(`^[0-9a-f]{${OPERATION_DIGEST_LENGTH}}$`);\n\n/**\n * Canonical JSON for a resolved YAML value: object keys sorted, arrays left in\n * order, everything else as JSON writes it.\n *\n * Key ORDER must not matter, because the merge's own idea of \"this operation\n * differs\" is `isDeepStrictEqual` (core/openapi-merge.ts), which ignores it. A\n * digest that disagreed with that comparison would go stale over a reordered\n * `summary`/`operationId` pair the merge itself calls identical — a false\n * collision on the one check whose worth is that it fires only for real ones.\n */\nfunction canonicalJson(node: unknown): string {\n if (node === null || typeof node !== \"object\") return JSON.stringify(node) ?? \"null\";\n if (Array.isArray(node)) return `[${node.map(canonicalJson).join(\",\")}]`;\n const entries = Object.entries(node as Record<string, unknown>)\n .filter(([, value]) => value !== undefined)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n return `{${entries.map(([key, value]) => `${JSON.stringify(key)}:${canonicalJson(value)}`).join(\",\")}}`;\n}\n\n/**\n * The operation without its own baseline marker — what a digest is taken over,\n * and what the merge writes.\n *\n * A pin is a statement ABOUT a delta, never part of the operation it describes.\n * Inside the digest input, an operation's identity would depend on the pin\n * pointing at it and no baseline could ever be self-consistent; inside the\n * merged contract, a living operation would carry a pin to a version of itself\n * and the next feature's baseline would hash the previous feature's bookkeeping.\n */\nexport function withoutOperationBaseline(node: unknown): unknown {\n if (node === null || typeof node !== \"object\" || Array.isArray(node)) return node;\n const record = node as Record<string, unknown>;\n if (!(OPENAPI_BASELINE_KEY in record)) return node;\n const rest = { ...record };\n delete rest[OPENAPI_BASELINE_KEY];\n return rest;\n}\n\n/** The identity of an operation's CONTENT: sha256 of its canonical form, pin excluded. */\nexport function operationDigest(node: unknown): string {\n return createHash(\"sha256\")\n .update(canonicalJson(withoutOperationBaseline(node)), \"utf8\")\n .digest(\"hex\")\n .slice(0, OPERATION_DIGEST_LENGTH);\n}\n\n/**\n * What a pin says, from digests alone — the one definition both the gate\n * (core/coherence.ts, which has parsed Operations) and the merge\n * (core/openapi-merge.ts, which has raw YAML trees) decide by. Two spellings of\n * this rule would eventually disagree about which operations a merge writes,\n * and the disagreement would be invisible until a contract came back wrong.\n *\n * Order matters: `quote` is settled against the delta's OWN content before the\n * living contract is consulted. An operation equal to its own baseline was not\n * edited, so what living holds now is nobody's business but the feature that\n * put it there.\n */\nexport function classifyBaselineDigests(\n pin: string | undefined,\n ownDigest: string,\n livingDigest: string | undefined,\n): \"unpinned\" | \"quote\" | \"edit\" | \"stale\" | \"unfounded\" {\n if (pin === undefined) return \"unpinned\";\n if (livingDigest === undefined) return \"unfounded\";\n if (pin === ownDigest) return \"quote\";\n if (pin === livingDigest) return \"edit\";\n return \"stale\";\n}\n\n/** The `x-loam-based-on` a node declares, or undefined — non-strings included, so validate can refuse them. */\nexport function operationBaselineOf(node: unknown): string | undefined {\n if (node === null || typeof node !== \"object\" || Array.isArray(node)) return undefined;\n const value = (node as Record<string, unknown>)[OPENAPI_BASELINE_KEY];\n if (value === undefined) return undefined;\n return typeof value === \"string\" ? value.trim() : String(value);\n}\n\n/** One operation as the reader sees it, including loam's feature-only removal marker. */\nexport interface Operation {\n id: string;\n /**\n * The standard per-operation OpenAPI `deprecated` flag — lifecycle\n * visibility, the documented first step of retiring an op.\n */\n deprecated: boolean;\n /**\n * `x-loam-remove: true` asks a FEATURE delta to remove this exact operation\n * on archive. The marker is never a callable operation and is illegal in a\n * living contract.\n */\n remove: boolean;\n /** Exact slot the marker/upsert addresses; removal is deliberately path+method-safe. */\n path: string;\n method: string;\n /**\n * `operationDigest` of this operation as it stands in ITS OWN document —\n * computed here so every caller compares content the one way the merge does.\n */\n digest: string;\n /**\n * The living version this operation was written against, from\n * `x-loam-based-on`. Feature deltas only: a living contract never carries one\n * (the merge strips it), and an adopted contract never had one.\n */\n basedOn?: string;\n}\n\n/** A `x-loam-remove: true` marker with no usable operationId — a slot named but no operation named. */\nexport interface AnonymousRemoval {\n path: string;\n method: string;\n}\n\n/** The parse of one OpenAPI document: its operations, and whether the file could be read at all. */\nexport interface OpenapiDoc {\n ops: Operation[];\n /**\n * operationIds this document defines in more than one (path, method) slot.\n * A LIVING contract with two slots claiming one id is ambiguous to every\n * check that joins on the id — a requirement's `Operations:` line, an edge's\n * `metadata { op }`, a removal marker — and the join silently picks one.\n */\n duplicateIds: string[];\n /**\n * Removal markers the operation reader cannot name. They are invisible to\n * every id-keyed check, so a delta carrying one used to archive cleanly and\n * write `x-loam-remove: true` straight into a living contract.\n */\n anonymousRemovals: AnonymousRemoval[];\n /**\n * Paths carrying `x-loam-remove: true` at PATH level, beside the methods\n * rather than inside one.\n *\n * Not an operation, so nothing keyed by (path, method) could see it — and this\n * reader is method-keyed like the merge was. A marker written there addressed\n * no operation, so archive copied it into the living contract verbatim, where\n * it was invisible to `openapi.remove-marker-living` for the same reason and\n * kept the empty-path cleanup from ever firing. Reported as its own list\n * because the answer differs by document: in a FEATURE delta it is an\n * authoring mistake, in a LIVING contract it is a marker that must never have\n * been published.\n */\n pathLevelRemovals: string[];\n /**\n * True when the file EXISTS but cannot be read as an OpenAPI document —\n * broken YAML, or a document that is not a mapping. A missing file is not\n * unreadable (absence is `service.no-openapi`'s question), and an empty one\n * parses to null and honestly defines nothing.\n */\n unreadable: boolean;\n /** The parser's own message, when there is one to quote back. */\n error?: string;\n}\n\n/**\n * Extract operations from an OpenAPI document by walking the parsed YAML\n * structure (paths.<path>.<method>, method filtered to the HTTP set).\n * Structure-aware on purpose: a regex scan both drops legal ids (kebab-case,\n * dotted) and picks up phantom ids from description text. Each operation\n * carries its id and its `deprecated: true` flag (exactly `true` counts —\n * strings and truthy noise do not). `ops` is the defined set, keyed by the\n * (path, method) SLOT — not by operationId.\n *\n * Keying by id used to drop the second slot claiming a name, which made the\n * single most common contract edit invisible: relocating an endpoint is a\n * removal marker at the old slot plus an upsert at the new one, the SAME\n * operationId twice. Whichever came second in document order vanished, so\n * either the marker addressed a slot loam could not see (`remove-target-missing`\n * on a perfectly good delta) or the new definition did. Two slots claiming one\n * id in a LIVING contract is a real ambiguity rather than a merge failure, so\n * it rides out as `duplicateIds` for the caller to grade.\n *\n * An unreadable document yields [] ops PLUS the `unreadable` flag. Parsers\n * never diagnose — the policy of every reader here — so the failure travels as\n * a flag for validate to grade (`openapi.invalid`). Swallowing it into a bare\n * [] made a broken contract indistinguishable from an EMPTY one, and the spine\n * check then reported every inbound edge broken (`spine.op-undefined`) — a\n * false diagnosis pointing at the landscape when the truth was this file.\n */\nexport async function readOpenapi(openapiPath: string, context?: FleetContext): Promise<OpenapiDoc> {\n if (context !== undefined) return context.readOpenapi(openapiPath);\n if (!existsSync(openapiPath)) return empty();\n const bytes = await readFile(openapiPath);\n // A contract whose bytes are not UTF-8 is as unreadable as broken YAML, and\n // for a worse reason: `toString(\"utf8\")` would substitute U+FFFD and hand the\n // parser a document nobody wrote. It travels as `unreadable` so validate\n // grades it `openapi.invalid` — the file is the error, and every check that\n // reads the contract stays suspended.\n if (!isUtf8(bytes)) return { ...empty(), unreadable: true, error: \"file is not valid UTF-8\" };\n let doc: unknown;\n try {\n doc = parse(bytes.toString(\"utf8\"));\n } catch (e) {\n return { ...empty(), unreadable: true, error: e instanceof Error ? e.message : String(e) };\n }\n // A scalar or sequence document is as unreadable as broken YAML: there is no\n // mapping to look `paths` up in, so nothing can be concluded from it. null\n // (an empty file) stays readable — it defines nothing, and says so honestly.\n if (doc !== null && (typeof doc !== \"object\" || Array.isArray(doc))) {\n return { ...empty(), unreadable: true, error: \"document is not a YAML mapping\" };\n }\n const paths = (doc as { paths?: unknown } | null)?.paths;\n if (!paths || typeof paths !== \"object\") return empty();\n const ops: Operation[] = [];\n const anonymousRemovals: AnonymousRemoval[] = [];\n const pathLevelRemovals: string[] = [];\n const seen = new Map<string, number>();\n for (const [path, item] of Object.entries(paths as Record<string, unknown>)) {\n if (!item || typeof item !== \"object\") continue;\n if ((item as Record<string, unknown>)[\"x-loam-remove\"] === true) pathLevelRemovals.push(path);\n for (const [method, op] of Object.entries(item as Record<string, unknown>)) {\n if (!HTTP_METHODS.has(method)) continue;\n if (!op || typeof op !== \"object\") continue;\n const id = (op as Record<string, unknown>)[\"operationId\"];\n const remove = (op as Record<string, unknown>)[\"x-loam-remove\"] === true;\n if (typeof id !== \"string\" || id.length === 0) {\n if (remove) anonymousRemovals.push({ path, method });\n continue;\n }\n seen.set(id, (seen.get(id) ?? 0) + 1);\n const basedOn = operationBaselineOf(op);\n ops.push({\n id,\n deprecated: (op as Record<string, unknown>)[\"deprecated\"] === true,\n remove,\n path,\n method,\n digest: operationDigest(op),\n ...(basedOn === undefined ? {} : { basedOn }),\n });\n }\n }\n const duplicateIds = [...seen].filter(([, n]) => n > 1).map(([id]) => id);\n return { ops, duplicateIds, anonymousRemovals, pathLevelRemovals, unreadable: false };\n}\n\nfunction empty(): OpenapiDoc {\n return { ops: [], duplicateIds: [], anonymousRemovals: [], pathLevelRemovals: [], unreadable: false };\n}\n\n/** The operations alone — for every caller whose own finding already covers the unreadable case. */\nexport async function operations(openapiPath: string, context?: FleetContext): Promise<Operation[]> {\n return (await readOpenapi(openapiPath, context)).ops;\n}\n\n/** The operationIds alone — `operations` for every caller that asks only \"does it exist\". */\nexport async function operationIds(openapiPath: string, context?: FleetContext): Promise<string[]> {\n return (await operations(openapiPath, context)).map((o) => o.id);\n}\n\n/**\n * The operationIds a service will provide after a feature is applied: living\n * operations, plus feature upserts, minus explicit feature removals.\n *\n * Removals are applied BEFORE upserts, which is what makes the answer\n * independent of the order the feature's document happens to spell them in.\n * Interleaving them one operation at a time meant a relocation — the same id\n * removed from `/old` and defined on `/new` — answered \"defined\" or \"gone\"\n * depending on which path sorted first in the YAML, and the requirement\n * governing it was reported undefined (`spec-api.op-undefined`) on exactly one\n * of the two spellings of the same change.\n */\nexport async function serviceOperationIds(\n docsDir: string,\n service: string,\n featureDir?: string,\n context?: FleetContext,\n): Promise<string[]> {\n if (context !== undefined) return context.serviceOperationIds(docsDir, service, featureDir);\n const ids = new Set(\n (await operations(servicePaths(docsDir, service).openapi)).filter((op) => !op.remove).map((op) => op.id),\n );\n if (featureDir) {\n const featOps = await operations(featureSpecPaths(featureDir, service).openapi);\n for (const op of featOps) if (op.remove) ids.delete(op.id);\n for (const op of featOps) if (!op.remove) ids.add(op.id);\n }\n return [...ids];\n}\n"]}
1
+ {"version":3,"file":"openapi.js","sourceRoot":"","sources":["../../src/core/openapi.ts"],"names":[],"mappings":"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","sourcesContent":["import { isUtf8 } from \"node:buffer\";\nimport { createHash } from \"node:crypto\";\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { parse } from \"yaml\";\nimport type { FleetContext } from \"./fleet-context.js\";\nimport { featureSpecPaths, servicePaths } from \"./repo.js\";\n\n/**\n * The path-item keys that hold operations. A path item also carries `summary`,\n * `parameters`, `servers` and vendor `x-*` extensions — an object-valued one with\n * an `operationId` inside (x-legacy and friends) is not an operation, and a\n * phantom id from it would make a broken contract look \"available\" to the\n * op-exists checks. Shared with archive's merge, which asks the same question\n * of the same keys.\n */\nexport const HTTP_METHODS = new Set([\"get\", \"put\", \"post\", \"delete\", \"options\", \"head\", \"patch\", \"trace\"]);\n\n/**\n * The baseline marker: which living version of this operation a FEATURE delta\n * was written against. Same idea as a requirement's `Based-On:` (core/spec.ts),\n * in the vendor-extension shape this axis already uses for `x-loam-remove`.\n *\n * This axis needs it MORE than the requirement axis does, because a feature's\n * openapi.yaml is a COMPLETE document rather than a patch: authors restate the\n * living contract around the slot they are changing, and the merge upserts every\n * operation the document spells. So a feature that never meant to touch\n * `refundOrder` — it merely quoted it — pushes its authoring-time copy back over\n * whatever landed in between, reverting another team's shipped change without\n * either feature overlapping the other at all. The pin is what lets the merge\n * tell a QUOTE from an EDIT: a quote equals its own baseline, and is skipped.\n */\nexport const OPENAPI_BASELINE_KEY = \"x-loam-based-on\";\n\n/** How much of the sha256 an `x-loam-based-on` carries — the length every loam digest uses. */\nexport const OPERATION_DIGEST_LENGTH = 16;\n\n/** The value shape of an `x-loam-based-on`, exactly as `operationDigest` writes it. */\nexport const OPERATION_DIGEST_RE = new RegExp(`^[0-9a-f]{${OPERATION_DIGEST_LENGTH}}$`);\n\n/**\n * Canonical JSON for a resolved YAML value: object keys sorted, arrays left in\n * order, everything else as JSON writes it.\n *\n * Key ORDER must not matter, because the merge's own idea of \"this operation\n * differs\" is `isDeepStrictEqual` (core/openapi-merge.ts), which ignores it. A\n * digest that disagreed with that comparison would go stale over a reordered\n * `summary`/`operationId` pair the merge itself calls identical — a false\n * collision on the one check whose worth is that it fires only for real ones.\n */\nfunction canonicalJson(node: unknown): string {\n if (node === null || typeof node !== \"object\") return JSON.stringify(node) ?? \"null\";\n if (Array.isArray(node)) return `[${node.map(canonicalJson).join(\",\")}]`;\n const entries = Object.entries(node as Record<string, unknown>)\n .filter(([, value]) => value !== undefined)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n return `{${entries.map(([key, value]) => `${JSON.stringify(key)}:${canonicalJson(value)}`).join(\",\")}}`;\n}\n\n/**\n * The operation without its own baseline marker — what a digest is taken over,\n * and what the merge writes.\n *\n * A pin is a statement ABOUT a delta, never part of the operation it describes.\n * Inside the digest input, an operation's identity would depend on the pin\n * pointing at it and no baseline could ever be self-consistent; inside the\n * merged contract, a living operation would carry a pin to a version of itself\n * and the next feature's baseline would hash the previous feature's bookkeeping.\n */\nexport function withoutOperationBaseline(node: unknown): unknown {\n if (node === null || typeof node !== \"object\" || Array.isArray(node)) return node;\n const record = node as Record<string, unknown>;\n if (!(OPENAPI_BASELINE_KEY in record)) return node;\n const rest = { ...record };\n delete rest[OPENAPI_BASELINE_KEY];\n return rest;\n}\n\n/** The identity of an operation's CONTENT: sha256 of its canonical form, pin excluded. */\nexport function operationDigest(node: unknown): string {\n return createHash(\"sha256\")\n .update(canonicalJson(withoutOperationBaseline(node)), \"utf8\")\n .digest(\"hex\")\n .slice(0, OPERATION_DIGEST_LENGTH);\n}\n\n/**\n * What a pin says, from digests alone — the one definition both the gate\n * (core/coherence.ts, which has parsed Operations) and the merge\n * (core/openapi-merge.ts, which has raw YAML trees) decide by. Two spellings of\n * this rule would eventually disagree about which operations a merge writes,\n * and the disagreement would be invisible until a contract came back wrong.\n *\n * Order matters: `quote` is settled against the delta's OWN content before the\n * living contract is consulted. An operation equal to its own baseline was not\n * edited, so what living holds now is nobody's business but the feature that\n * put it there.\n */\nexport function classifyBaselineDigests(\n pin: string | undefined,\n ownDigest: string,\n livingDigest: string | undefined,\n): \"unpinned\" | \"quote\" | \"edit\" | \"stale\" | \"unfounded\" {\n if (pin === undefined) return \"unpinned\";\n if (livingDigest === undefined) return \"unfounded\";\n if (pin === ownDigest) return \"quote\";\n if (pin === livingDigest) return \"edit\";\n return \"stale\";\n}\n\n/** The `x-loam-based-on` a node declares, or undefined — non-strings included, so validate can refuse them. */\nexport function operationBaselineOf(node: unknown): string | undefined {\n if (node === null || typeof node !== \"object\" || Array.isArray(node)) return undefined;\n const value = (node as Record<string, unknown>)[OPENAPI_BASELINE_KEY];\n if (value === undefined) return undefined;\n return typeof value === \"string\" ? value.trim() : String(value);\n}\n\n/** One operation as the reader sees it, including loam's feature-only removal marker. */\nexport interface Operation {\n id: string;\n /**\n * The standard per-operation OpenAPI `deprecated` flag — lifecycle\n * visibility, the documented first step of retiring an op.\n */\n deprecated: boolean;\n /**\n * `x-loam-remove: true` asks a FEATURE delta to remove this exact operation\n * on archive. The marker is never a callable operation and is illegal in a\n * living contract.\n */\n remove: boolean;\n /** Exact slot the marker/upsert addresses; removal is deliberately path+method-safe. */\n path: string;\n method: string;\n /**\n * `operationDigest` of this operation as it stands in ITS OWN document —\n * computed here so every caller compares content the one way the merge does.\n */\n digest: string;\n /**\n * The living version this operation was written against, from\n * `x-loam-based-on`. Feature deltas only: a living contract never carries one\n * (the merge strips it), and an adopted contract never had one.\n */\n basedOn?: string;\n}\n\n/** A `x-loam-remove: true` marker with no usable operationId — a slot named but no operation named. */\nexport interface AnonymousRemoval {\n path: string;\n method: string;\n}\n\n/** The parse of one OpenAPI document: its operations, and whether the file could be read at all. */\nexport interface OpenapiDoc {\n ops: Operation[];\n /**\n * operationIds this document defines in more than one (path, method) slot.\n * A LIVING contract with two slots claiming one id is ambiguous to every\n * check that joins on the id — a requirement's `Operations:` line, an edge's\n * `metadata { op }`, a removal marker — and the join silently picks one.\n */\n duplicateIds: string[];\n /**\n * Removal markers the operation reader cannot name. They are invisible to\n * every id-keyed check, so a delta carrying one used to archive cleanly and\n * write `x-loam-remove: true` straight into a living contract.\n */\n anonymousRemovals: AnonymousRemoval[];\n /**\n * Paths carrying `x-loam-remove: true` at PATH level, beside the methods\n * rather than inside one.\n *\n * Not an operation, so nothing keyed by (path, method) could see it — and this\n * reader is method-keyed like the merge was. A marker written there addressed\n * no operation, so archive copied it into the living contract verbatim, where\n * it was invisible to `openapi.remove-marker-living` for the same reason and\n * kept the empty-path cleanup from ever firing. Reported as its own list\n * because the answer differs by document: in a FEATURE delta it is an\n * authoring mistake, in a LIVING contract it is a marker that must never have\n * been published.\n */\n pathLevelRemovals: string[];\n /**\n * True when the file EXISTS but cannot be read as an OpenAPI document —\n * broken YAML, or a document that is not a mapping. A missing file is not\n * unreadable (absence is `service.no-openapi`'s question), and an empty one\n * parses to null and honestly defines nothing.\n */\n unreadable: boolean;\n /** The parser's own message, when there is one to quote back. */\n error?: string;\n}\n\n/**\n * Extract operations from an OpenAPI document by walking the parsed YAML\n * structure (paths.<path>.<method>, method filtered to the HTTP set).\n * Structure-aware on purpose: a regex scan both drops legal ids (kebab-case,\n * dotted) and picks up phantom ids from description text. Each operation\n * carries its id and its `deprecated: true` flag (exactly `true` counts —\n * strings and truthy noise do not). `ops` is the defined set, keyed by the\n * (path, method) SLOT — not by operationId.\n *\n * Keying by id used to drop the second slot claiming a name, which made the\n * single most common contract edit invisible: relocating an endpoint is a\n * removal marker at the old slot plus an upsert at the new one, the SAME\n * operationId twice. Whichever came second in document order vanished, so\n * either the marker addressed a slot loam could not see (`remove-target-missing`\n * on a perfectly good delta) or the new definition did. Two slots claiming one\n * id in a LIVING contract is a real ambiguity rather than a merge failure, so\n * it rides out as `duplicateIds` for the caller to grade.\n *\n * An unreadable document yields [] ops PLUS the `unreadable` flag. Parsers\n * never diagnose — the policy of every reader here — so the failure travels as\n * a flag for validate to grade (`openapi.invalid`). Swallowing it into a bare\n * [] made a broken contract indistinguishable from an EMPTY one, and the spine\n * check then reported every inbound edge broken (`spine.op-undefined`) — a\n * false diagnosis pointing at the landscape when the truth was this file.\n */\nexport async function readOpenapi(openapiPath: string, context?: FleetContext): Promise<OpenapiDoc> {\n if (context !== undefined) return context.readOpenapi(openapiPath);\n if (!existsSync(openapiPath)) return empty();\n const bytes = await readFile(openapiPath);\n // A contract whose bytes are not UTF-8 is as unreadable as broken YAML, and\n // for a worse reason: `toString(\"utf8\")` would substitute U+FFFD and hand the\n // parser a document nobody wrote. It travels as `unreadable` so validate\n // grades it `openapi.invalid` — the file is the error, and every check that\n // reads the contract stays suspended.\n if (!isUtf8(bytes)) return { ...empty(), unreadable: true, error: \"file is not valid UTF-8\" };\n let doc: unknown;\n try {\n doc = parse(bytes.toString(\"utf8\"));\n } catch (e) {\n return { ...empty(), unreadable: true, error: e instanceof Error ? e.message : String(e) };\n }\n // A scalar or sequence document is as unreadable as broken YAML: there is no\n // mapping to look `paths` up in, so nothing can be concluded from it. null\n // (an empty file) stays readable — it defines nothing, and says so honestly.\n if (doc !== null && (typeof doc !== \"object\" || Array.isArray(doc))) {\n return { ...empty(), unreadable: true, error: \"document is not a YAML mapping\" };\n }\n const paths = (doc as { paths?: unknown } | null)?.paths;\n if (!paths || typeof paths !== \"object\") return empty();\n const ops: Operation[] = [];\n const anonymousRemovals: AnonymousRemoval[] = [];\n const pathLevelRemovals: string[] = [];\n const seen = new Map<string, number>();\n for (const [path, item] of Object.entries(paths as Record<string, unknown>)) {\n if (!item || typeof item !== \"object\") continue;\n if ((item as Record<string, unknown>)[\"x-loam-remove\"] === true) pathLevelRemovals.push(path);\n for (const [method, op] of Object.entries(item as Record<string, unknown>)) {\n if (!HTTP_METHODS.has(method)) continue;\n if (!op || typeof op !== \"object\") continue;\n const id = (op as Record<string, unknown>)[\"operationId\"];\n const remove = (op as Record<string, unknown>)[\"x-loam-remove\"] === true;\n if (typeof id !== \"string\" || id.length === 0) {\n if (remove) anonymousRemovals.push({ path, method });\n continue;\n }\n seen.set(id, (seen.get(id) ?? 0) + 1);\n const basedOn = operationBaselineOf(op);\n ops.push({\n id,\n deprecated: (op as Record<string, unknown>)[\"deprecated\"] === true,\n remove,\n path,\n method,\n digest: operationDigest(op),\n ...(basedOn === undefined ? {} : { basedOn }),\n });\n }\n }\n const duplicateIds = [...seen].filter(([, n]) => n > 1).map(([id]) => id);\n return { ops, duplicateIds, anonymousRemovals, pathLevelRemovals, unreadable: false };\n}\n\nfunction empty(): OpenapiDoc {\n return { ops: [], duplicateIds: [], anonymousRemovals: [], pathLevelRemovals: [], unreadable: false };\n}\n\n/** The operations alone — for every caller whose own finding already covers the unreadable case. */\nexport async function operations(openapiPath: string, context?: FleetContext): Promise<Operation[]> {\n return (await readOpenapi(openapiPath, context)).ops;\n}\n\n/** The operationIds alone — `operations` for every caller that asks only \"does it exist\". */\nexport async function operationIds(openapiPath: string, context?: FleetContext): Promise<string[]> {\n return (await operations(openapiPath, context)).map((o) => o.id);\n}\n\n/**\n * The operationIds a service will provide after a feature is applied: living\n * operations, plus feature upserts, minus explicit feature removals.\n *\n * Removals are applied BEFORE upserts, which is what makes the answer\n * independent of the order the feature's document happens to spell them in.\n * Interleaving them one operation at a time meant a relocation — the same id\n * removed from `/old` and defined on `/new` — answered \"defined\" or \"gone\"\n * depending on which path sorted first in the YAML, and the requirement\n * governing it was reported undefined (`spec-api.op-undefined`) on exactly one\n * of the two spellings of the same change.\n *\n * A context is threaded into the READS and never asked for the answer. It used\n * to own a second copy of this function, and that copy interleaved removals\n * with upserts — the exact bug the paragraph above describes as fixed. So the\n * commands that pass a context (`validate`, `status`) and the one that does not\n * (`archive`) disagreed about whether a relocated operation still existed, and\n * `spec-api.op-undefined` — an error that gates archive — fired or not\n * depending on which path key the author happened to type first. Memoising is\n * the context's job; the ordering rule is this function's, and there is one of\n * it.\n */\nexport async function serviceOperationIds(\n docsDir: string,\n service: string,\n featureDir?: string,\n context?: FleetContext,\n): Promise<string[]> {\n const ids = new Set(\n (await operations(servicePaths(docsDir, service).openapi, context)).filter((op) => !op.remove).map((op) => op.id),\n );\n // Absence is `undefined`, the shape the optional parameter declares. The\n // truthiness test also read \"\" as \"no feature\", which is a caller mistake\n // worth surfacing rather than answering as though the feature carried no\n // contract delta.\n if (featureDir !== undefined) {\n const featOps = await operations(featureSpecPaths(featureDir, service).openapi, context);\n for (const op of featOps) if (op.remove) ids.delete(op.id);\n for (const op of featOps) if (!op.remove) ids.add(op.id);\n }\n return [...ids];\n}\n"]}
@@ -5,6 +5,8 @@ import { existsSync } from "node:fs";
5
5
  import { lstat, readFile, readdir, realpath, stat } from "node:fs/promises";
6
6
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
7
7
  import { parse as parseYaml } from "yaml";
8
+ import { asRecord, dictionary, ownValue } from "./records.js";
9
+ import { FEATURE_ID_RULE, isFeatureId } from "./ids.js";
8
10
  import { compareIds } from "./repo.js";
9
11
  import { isRequirementsHeading, parseRequirements, REQUIREMENT_ID_RE, requirementIdDeclarations, requirementIdProblems, sectionHeadings, } from "./spec.js";
10
12
  /** Released behavior and the newer upstream snapshot used as a compatibility canary. */
@@ -19,6 +21,32 @@ export const OPENSPEC_BASELINES = Object.freeze({
19
21
  commit: "45cca5db6137ed209117cc70510eb3e057fb981b",
20
22
  }),
21
23
  });
24
+ /**
25
+ * The dispositions as values, not only as a type, because a mapping file names
26
+ * one as a plain string and the parse boundary has to be able to ask whether
27
+ * that string is one of these. A hand-written second spelling of the same list
28
+ * is how a disposition added here would go on being unrecognised where it is
29
+ * read from somebody else's YAML.
30
+ */
31
+ export const OPENSPEC_ARTIFACT_DISPOSITIONS = [
32
+ "translate-project-context",
33
+ "record-external-planning-root",
34
+ "remove-after-cutover",
35
+ "map-requirements",
36
+ "review-as-service-adr",
37
+ "translate-change-metadata",
38
+ "convert-to-intent",
39
+ "preserve-as-legacy-checklist",
40
+ "review-as-feature-adr",
41
+ "map-delta",
42
+ "review-custom-workflow",
43
+ "retain-read-only",
44
+ "manual-review",
45
+ ];
46
+ /** Whether an authored string names a disposition this codebase actually defines. */
47
+ export function isOpenSpecArtifactDisposition(value) {
48
+ return OPENSPEC_ARTIFACT_DISPOSITIONS.includes(value);
49
+ }
22
50
  export class OpenSpecRootError extends Error {
23
51
  constructor(message) {
24
52
  super(message);
@@ -166,16 +194,13 @@ async function isSymbolicLink(path) {
166
194
  return false;
167
195
  }
168
196
  }
169
- function asRecord(value) {
170
- return typeof value === "object" && value !== null && !Array.isArray(value)
171
- ? value
172
- : null;
173
- }
174
- function dictionary() {
175
- return Object.create(null);
176
- }
177
- function ownValue(record, key) {
178
- return Object.prototype.hasOwnProperty.call(record, key) ? record[key] : undefined;
197
+ /**
198
+ * `Array.isArray` narrows an `unknown` to `any[]`, which is how a checked
199
+ * element type went on being asserted back in a line later. Deciding both
200
+ * halves in one predicate makes the narrowing carry the check.
201
+ */
202
+ function isStringArray(value) {
203
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
179
204
  }
180
205
  async function yamlRecord(path) {
181
206
  const parsed = parseYaml(await readFile(path, "utf8"));
@@ -338,6 +363,14 @@ function inspectChangeShape(path, raw, scope, renames, unsupported) {
338
363
  issue(unsupported, scope, "openspec.change-requirements-outside-delta-sections", path, `${stranded.length} requirement(s) would be stranded outside delta sections.`);
339
364
  }
340
365
  }
366
+ /**
367
+ * The parsed requirements come back with the counts because both callers wanted
368
+ * them and each opened the file a second time to get its own copy — two reads
369
+ * of the same bytes per spec.md, and two readings of it: the counts came from
370
+ * this parse while the requirement names, and the living corpus every rename is
371
+ * resolved against, came from the other. A file edited mid-audit could be
372
+ * counted from one of them and routed from the other.
373
+ */
341
374
  async function inspectSpecFile(root, absolute, kind, scope, mapping, renamed, unsupported) {
342
375
  const path = portable(root, absolute);
343
376
  const raw = await readFile(absolute, "utf8");
@@ -364,7 +397,7 @@ async function inspectSpecFile(root, absolute, kind, scope, mapping, renamed, un
364
397
  renamed.push(...usages);
365
398
  inspectChangeShape(path, raw, changeScope, usages, unsupported);
366
399
  }
367
- return { path, counts };
400
+ return { path, counts, requirements };
368
401
  }
369
402
  async function inspectConfig(root, unsupported) {
370
403
  const path = join(root, "config.yaml");
@@ -438,8 +471,8 @@ async function inspectChangeMetadata(root, dir, scope, fallbackSchema, unsupport
438
471
  invalid("goal must be a non-empty string when present.");
439
472
  }
440
473
  if ("affected_areas" in metadata
441
- && (!Array.isArray(metadata.affected_areas)
442
- || metadata.affected_areas.some((value) => typeof value !== "string" || value.length === 0))) {
474
+ && (!isStringArray(metadata.affected_areas)
475
+ || metadata.affected_areas.some((value) => value.length === 0))) {
443
476
  invalid("affected_areas must be an array of non-empty strings when present.");
444
477
  }
445
478
  if ("initiative" in metadata) {
@@ -549,22 +582,19 @@ function validArtifactSchema(document) {
549
582
  || typeof artifact.template !== "string"
550
583
  || artifact.template.length === 0
551
584
  || ("instruction" in artifact && typeof artifact.instruction !== "string")
552
- || ("requires" in artifact
553
- && (!Array.isArray(artifact.requires)
554
- || artifact.requires.some((dependency) => typeof dependency !== "string")))) {
585
+ || ("requires" in artifact && !isStringArray(artifact.requires))) {
555
586
  return false;
556
587
  }
557
588
  artifacts.push({
558
589
  id: artifact.id,
559
- requires: Array.isArray(artifact.requires) ? artifact.requires : [],
590
+ requires: isStringArray(artifact.requires) ? artifact.requires : [],
560
591
  });
561
592
  }
562
593
  if ("apply" in document) {
563
594
  const apply = asRecord(document.apply);
564
595
  if (apply === null
565
- || !Array.isArray(apply.requires)
596
+ || !isStringArray(apply.requires)
566
597
  || apply.requires.length === 0
567
- || apply.requires.some((dependency) => typeof dependency !== "string")
568
598
  || ("tracks" in apply && apply.tracks !== null && typeof apply.tracks !== "string")
569
599
  || ("instruction" in apply && typeof apply.instruction !== "string")) {
570
600
  return false;
@@ -630,11 +660,10 @@ async function inventoryChanges(root, base, ids, scope, fallbackSchema, mapping,
630
660
  for (const specFile of specFiles) {
631
661
  const inspected = await inspectSpecFile(root, specFile, "change", scope, mapping, renamed, unsupported);
632
662
  files.push(inspected.path);
633
- const parsed = parseRequirements(await readFile(specFile, "utf8"));
634
663
  specs.push({
635
664
  capability: portable(specRoot, dirname(specFile)),
636
665
  path: inspected.path,
637
- requirementNames: [...new Set(parsed.filter((item) => item.kind !== "BASE").map((item) => item.name))].sort(compareIds),
666
+ requirementNames: [...new Set(inspected.requirements.filter((item) => item.kind !== "BASE").map((item) => item.name))].sort(compareIds),
638
667
  ...inspected.counts,
639
668
  });
640
669
  addCounts(counts, inspected.counts);
@@ -729,7 +758,6 @@ function artifactFor(root, absolute) {
729
758
  function sortIssues(issues) {
730
759
  issues.sort((a, b) => compareIds(a.path, b.path) || compareIds(a.code, b.code));
731
760
  }
732
- const LOAM_FEATURE_ID_RE = /^[A-Za-z][A-Za-z0-9]*-\d+$/;
733
761
  function titleFromChangeId(id) {
734
762
  const words = id.split(/[-_]+/).filter(Boolean);
735
763
  const text = words.join(" ");
@@ -781,7 +809,13 @@ export function createOpenSpecMappingSkeleton(inventory) {
781
809
  from: rename.from,
782
810
  to: rename.to,
783
811
  existingRequirementId: rename.existingRequirementId,
784
- requirementId: rename.status === "mapped" ? rename.existingRequirementId : null,
812
+ // The identity the inventory settled on, which is the field the
813
+ // skeleton is asking a human to fill in. `existingRequirementId` is
814
+ // the living source's own id and happens to equal it on the only path
815
+ // that reaches here today — but a caller that audits WITH a mapping
816
+ // would have a settled id and no existing one, and the skeleton would
817
+ // hand that decision back as unmade.
818
+ requirementId: rename.requirementId,
785
819
  }])),
786
820
  artifacts: Object.fromEntries(inventory.artifactDecisions.map((decision) => [
787
821
  decision.path,
@@ -859,7 +893,7 @@ export async function inventoryOpenSpec(input, options = {}) {
859
893
  for (const specFile of livingFiles) {
860
894
  const id = portable(specsRoot, dirname(specFile));
861
895
  const inspected = await inspectSpecFile(root, specFile, "living", null, mapping, renamed, blockers);
862
- livingRequirements.set(id, parseRequirements(await readFile(specFile, "utf8")));
896
+ livingRequirements.set(id, inspected.requirements);
863
897
  const capability = { id, files: [inspected.path], ...inspected.counts };
864
898
  capabilities.push(capability);
865
899
  addCounts(livingCounts, inspected.counts);
@@ -1105,11 +1139,11 @@ export async function inventoryOpenSpec(input, options = {}) {
1105
1139
  message: `Active change '${change.id}' needs a non-empty title.`,
1106
1140
  });
1107
1141
  }
1108
- if (feature !== null && !LOAM_FEATURE_ID_RE.test(feature)) {
1142
+ if (feature !== null && !isFeatureId(feature)) {
1109
1143
  mappingIssues.push({
1110
1144
  code: "mapping.feature-id-invalid",
1111
1145
  key: change.id,
1112
- message: `Active change '${change.id}' maps to invalid loam feature id '${feature}'. Expected <word>-<number>.`,
1146
+ message: `Active change '${change.id}' maps to invalid loam feature id '${feature}'. ${FEATURE_ID_RULE}`,
1113
1147
  });
1114
1148
  }
1115
1149
  return {
@@ -1123,7 +1157,7 @@ export async function inventoryOpenSpec(input, options = {}) {
1123
1157
  });
1124
1158
  const featureOwners = new Map();
1125
1159
  for (const decision of changeDecisions) {
1126
- if (decision.feature === null || !LOAM_FEATURE_ID_RE.test(decision.feature))
1160
+ if (decision.feature === null || !isFeatureId(decision.feature))
1127
1161
  continue;
1128
1162
  const key = decision.feature.toLowerCase();
1129
1163
  const owners = featureOwners.get(key) ?? [];
@@ -1313,6 +1347,9 @@ export async function inventoryOpenSpec(input, options = {}) {
1313
1347
  };
1314
1348
  });
1315
1349
  const artifactPaths = new Set(artifactDecisions.map((decision) => decision.path));
1350
+ // Sets of plain strings: the question being asked is "is this authored value
1351
+ // one of the ones this kind accepts", and a set of the union could not be
1352
+ // asked it without an assertion putting the answer back where it started.
1316
1353
  const dispositionsByKind = {
1317
1354
  proposal: new Set(["convert-to-intent", "retain-read-only", "manual-review"]),
1318
1355
  tasks: new Set(["preserve-as-legacy-checklist", "retain-read-only", "manual-review"]),