@rsaglobaltech/specgate 0.27.9 → 1.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/dist/packages/core/src/application/ValidateChangeUseCase.js +19 -3
  3. package/dist/packages/core/src/application/ValidateChangeUseCase.js.map +1 -1
  4. package/dist/packages/core/src/domain/ApiContract.js +157 -0
  5. package/dist/packages/core/src/domain/ApiContract.js.map +1 -0
  6. package/dist/packages/core/src/domain/Approvals.js +127 -0
  7. package/dist/packages/core/src/domain/Approvals.js.map +1 -0
  8. package/dist/packages/core/src/domain/ChangeLayers.js +285 -0
  9. package/dist/packages/core/src/domain/ChangeLayers.js.map +1 -0
  10. package/dist/packages/core/src/domain/HarnessConfig.js.map +1 -1
  11. package/dist/packages/core/src/domain/HarnessGaps.js +182 -0
  12. package/dist/packages/core/src/domain/HarnessGaps.js.map +1 -0
  13. package/dist/packages/core/src/domain/HarnessReport.js +16 -1
  14. package/dist/packages/core/src/domain/HarnessReport.js.map +1 -1
  15. package/dist/packages/core/src/domain/HarnessRun.js.map +1 -1
  16. package/dist/packages/core/src/domain/Principles.js +119 -0
  17. package/dist/packages/core/src/domain/Principles.js.map +1 -0
  18. package/dist/packages/core/src/domain/RequirementGraph.js +12 -1
  19. package/dist/packages/core/src/domain/RequirementGraph.js.map +1 -1
  20. package/dist/packages/core/src/domain/Workspace.js +150 -0
  21. package/dist/packages/core/src/domain/Workspace.js.map +1 -0
  22. package/dist/packages/core/src/domain/WorkspaceStory.js +176 -0
  23. package/dist/packages/core/src/domain/WorkspaceStory.js.map +1 -0
  24. package/dist/packages/core/src/index.js +1 -0
  25. package/dist/packages/core/src/index.js.map +1 -1
  26. package/dist/packages/core/src/infrastructure/HarnessConfigFile.js +12 -0
  27. package/dist/packages/core/src/infrastructure/HarnessConfigFile.js.map +1 -1
  28. package/dist/packages/mcp-spec-driven/src/tools.js +18 -0
  29. package/dist/packages/mcp-spec-driven/src/tools.js.map +1 -1
  30. package/dist/scripts/alm/cli.js +45 -4
  31. package/dist/scripts/alm/cli.js.map +1 -1
  32. package/dist/scripts/alm/clients.js +3 -1
  33. package/dist/scripts/alm/clients.js.map +1 -1
  34. package/dist/scripts/alm/phases.js +279 -0
  35. package/dist/scripts/alm/phases.js.map +1 -0
  36. package/dist/scripts/alm/port.js +33 -5
  37. package/dist/scripts/alm/port.js.map +1 -1
  38. package/dist/scripts/alm/providers/azure.js +72 -11
  39. package/dist/scripts/alm/providers/azure.js.map +1 -1
  40. package/dist/scripts/alm/providers/github.js +67 -7
  41. package/dist/scripts/alm/providers/github.js.map +1 -1
  42. package/dist/scripts/alm/providers/index.js +8 -2
  43. package/dist/scripts/alm/providers/index.js.map +1 -1
  44. package/dist/scripts/alm/providers/jira.js +86 -8
  45. package/dist/scripts/alm/providers/jira.js.map +1 -1
  46. package/dist/scripts/alm/providers/linear.js +167 -0
  47. package/dist/scripts/alm/providers/linear.js.map +1 -0
  48. package/dist/scripts/change/artifacts.js +15 -1
  49. package/dist/scripts/change/artifacts.js.map +1 -1
  50. package/dist/scripts/change/instructions.js +47 -5
  51. package/dist/scripts/change/instructions.js.map +1 -1
  52. package/dist/scripts/cli/commands/alm/PhaseCommand.js +153 -0
  53. package/dist/scripts/cli/commands/alm/PhaseCommand.js.map +1 -0
  54. package/dist/scripts/cli/commands/alm/PullCommand.js +33 -2
  55. package/dist/scripts/cli/commands/alm/PullCommand.js.map +1 -1
  56. package/dist/scripts/cli/commands/alm/pull-back.js +104 -0
  57. package/dist/scripts/cli/commands/alm/pull-back.js.map +1 -0
  58. package/dist/scripts/cli/commands/change/ChangeCommand.js +149 -15
  59. package/dist/scripts/cli/commands/change/ChangeCommand.js.map +1 -1
  60. package/dist/scripts/cli/commands/change/TicketCommand.js +20 -7
  61. package/dist/scripts/cli/commands/change/TicketCommand.js.map +1 -1
  62. package/dist/scripts/cli/commands/harness/RunCommand.js +64 -20
  63. package/dist/scripts/cli/commands/harness/RunCommand.js.map +1 -1
  64. package/dist/scripts/cli/commands/project/AdoptCommand.js +44 -4
  65. package/dist/scripts/cli/commands/project/AdoptCommand.js.map +1 -1
  66. package/dist/scripts/cli/commands/project/InitProjectCommand.js +9 -14
  67. package/dist/scripts/cli/commands/project/InitProjectCommand.js.map +1 -1
  68. package/dist/scripts/cli/commands/project/MigrateCommand.js +166 -0
  69. package/dist/scripts/cli/commands/project/MigrateCommand.js.map +1 -0
  70. package/dist/scripts/cli/commands/project/OnboardCommand.js +23 -5
  71. package/dist/scripts/cli/commands/project/OnboardCommand.js.map +1 -1
  72. package/dist/scripts/cli/commands/quality/CheckCommand.js +25 -0
  73. package/dist/scripts/cli/commands/quality/CheckCommand.js.map +1 -1
  74. package/dist/scripts/cli/commands/quality/ValidateSpecsCommand.js +15 -0
  75. package/dist/scripts/cli/commands/quality/ValidateSpecsCommand.js.map +1 -1
  76. package/dist/scripts/cli/commands/spec/ContractCommand.js +117 -0
  77. package/dist/scripts/cli/commands/spec/ContractCommand.js.map +1 -0
  78. package/dist/scripts/cli/commands/spec/DoneCommand.js +22 -0
  79. package/dist/scripts/cli/commands/spec/DoneCommand.js.map +1 -1
  80. package/dist/scripts/cli/commands/spec/DraftCommand.js +22 -8
  81. package/dist/scripts/cli/commands/spec/DraftCommand.js.map +1 -1
  82. package/dist/scripts/cli/commands/spec/DriftCommand.js +128 -0
  83. package/dist/scripts/cli/commands/spec/DriftCommand.js.map +1 -0
  84. package/dist/scripts/cli/commands/spec/StatusCommand.js +85 -0
  85. package/dist/scripts/cli/commands/spec/StatusCommand.js.map +1 -1
  86. package/dist/scripts/cli/commands/spec/StudioCommand.js +48 -6
  87. package/dist/scripts/cli/commands/spec/StudioCommand.js.map +1 -1
  88. package/dist/scripts/cli/commands/workspace/WorkspaceCommand.js +612 -0
  89. package/dist/scripts/cli/commands/workspace/WorkspaceCommand.js.map +1 -0
  90. package/dist/scripts/contract.js +10 -0
  91. package/dist/scripts/contract.js.map +1 -0
  92. package/dist/scripts/docs/nav.js +16 -1
  93. package/dist/scripts/docs/nav.js.map +1 -1
  94. package/dist/scripts/draft/drafting-guide.js +6 -0
  95. package/dist/scripts/draft/drafting-guide.js.map +1 -1
  96. package/dist/scripts/draft/ticket-guide.js +5 -0
  97. package/dist/scripts/draft/ticket-guide.js.map +1 -1
  98. package/dist/scripts/drift.js +10 -0
  99. package/dist/scripts/drift.js.map +1 -0
  100. package/dist/scripts/harness/gap-evidence.js +149 -0
  101. package/dist/scripts/harness/gap-evidence.js.map +1 -0
  102. package/dist/scripts/harness/prompt.js +54 -1
  103. package/dist/scripts/harness/prompt.js.map +1 -1
  104. package/dist/scripts/harness/report.js +104 -5
  105. package/dist/scripts/harness/report.js.map +1 -1
  106. package/dist/scripts/lib/approvals.js +243 -0
  107. package/dist/scripts/lib/approvals.js.map +1 -0
  108. package/dist/scripts/lib/change-review.js +19 -11
  109. package/dist/scripts/lib/change-review.js.map +1 -1
  110. package/dist/scripts/lib/contracts.js +211 -0
  111. package/dist/scripts/lib/contracts.js.map +1 -0
  112. package/dist/scripts/lib/drift.js +80 -0
  113. package/dist/scripts/lib/drift.js.map +1 -0
  114. package/dist/scripts/lib/live-model.js +1 -0
  115. package/dist/scripts/lib/live-model.js.map +1 -1
  116. package/dist/scripts/lib/migrate.js +264 -0
  117. package/dist/scripts/lib/migrate.js.map +1 -0
  118. package/dist/scripts/lib/role-packages.js +230 -0
  119. package/dist/scripts/lib/role-packages.js.map +1 -0
  120. package/dist/scripts/lib/steering.js +180 -0
  121. package/dist/scripts/lib/steering.js.map +1 -0
  122. package/dist/scripts/lib/studio-app-page.js +28 -5
  123. package/dist/scripts/lib/studio-app-page.js.map +1 -1
  124. package/dist/scripts/lib/studio-app.js +29 -0
  125. package/dist/scripts/lib/studio-app.js.map +1 -1
  126. package/dist/scripts/lib/studio-route.js +20 -0
  127. package/dist/scripts/lib/studio-route.js.map +1 -1
  128. package/dist/scripts/lib/surface.js +55 -0
  129. package/dist/scripts/lib/surface.js.map +1 -1
  130. package/dist/scripts/lib/workspace-draft.js +146 -0
  131. package/dist/scripts/lib/workspace-draft.js.map +1 -0
  132. package/dist/scripts/lib/workspace-live-page.js +90 -0
  133. package/dist/scripts/lib/workspace-live-page.js.map +1 -0
  134. package/dist/scripts/lib/workspace-live.js +199 -0
  135. package/dist/scripts/lib/workspace-live.js.map +1 -0
  136. package/dist/scripts/lib/workspace.js +619 -0
  137. package/dist/scripts/lib/workspace.js.map +1 -0
  138. package/dist/scripts/migrate.js +10 -0
  139. package/dist/scripts/migrate.js.map +1 -0
  140. package/dist/scripts/schema/registry.js +12 -0
  141. package/dist/scripts/schema/registry.js.map +1 -1
  142. package/dist/scripts/workspace.js +10 -0
  143. package/dist/scripts/workspace.js.map +1 -0
  144. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -9,6 +9,63 @@ The release process is in [`docs/release-process.md`](docs/release-process.md).
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [1.0.0-rc.2] — 2026-10-30
13
+
14
+ After the first microservices run of truck-flote (five repositories, one story across three of them, nine requirements delivered in parallel): the workspace takes the client's brief and drafts its stories, a story's intent and criteria reach every repository, the story is built in the order of its dependencies, and `init --yes` adopts the folder it is run in.
15
+
16
+ ### Breaking
17
+
18
+ - **`specgate init --yes` adopts a folder that already has code** (REQ-249, modified): it scaffolded a separate project inside it (`my-spec-driven-app/`), so a scripted `init` in a repository made a second one. `--yes` now only skips the questions; `--new`, `--out`, `--config`, `--multi-stack` and `--forja` still scaffold.
19
+
20
+ ### Fixed
21
+
22
+ - **`change validate` reports what `change approve` refuses** (REQ-350): a layered change validated, then approval refused it for requirements with no kind. Validation now fails with `change_not_approvable` for each finding approval would block on.
23
+ - **The agent writing a change's requirements is told what they need** (REQ-351): `kind=`, `actor=`, and a feature file of its own.
24
+ - **Two requirements naming one feature file are reported** (REQ-352): `change validate` and `change archive` refuse with `change_feature_shared`; archiving would have kept only one's scenarios.
25
+ - **A delivered requirement ticks its tasks** (REQ-353): `done`, which the harness runs, ticks the tasks of the open change that name the requirement.
26
+ - **`change --help` describes the three layers** (REQ-354), and docs/migrating-to-1.0.md says how to install a candidate just published (`--prefer-online`) and how to move a forja project to it.
27
+ - **The workspace takes the client's brief** (REQ-339..341): `specgate workspace draft` puts a root `brief.md` below a front matter that names the brief's stories and their repositories, `workspace draft <story> --agent "<cmd>"` drafts a story's intent and criteria from the brief, confined to the story, and `specgate status` in a workspace repository shows the workspace and the next step instead of failing on a missing matrix.
28
+ - **Every repository receives the story** (REQ-342..344): `workspace propose` takes a drafted story, copies its intent and criteria into each sub-change's `discover.md` and names the story's other repositories and how to write `depends=` and `api=`; `workspace check` warns `workspace_story_diverged` when a repository's criteria are not the story's; each harness is told the story's other parts.
29
+ - **A story is built in dependency order** (REQ-345): `workspace harness run --story` starts a repository whose part waits on another's undelivered requirement after that repository has finished; the dry run shows the levels.
30
+ - **The live view and the story's cost** (REQ-346, REQ-347): every JSON document of the studio says `charset=utf-8`, and a story's cost counts its archived parts.
31
+ - **Short repository names, and the logs ignored** (REQ-348, REQ-349): `workspace init` names `truck-flote-fleet` as `fleet`, prints name, path and role in their own columns, and ignores `.specgate/workspace-runs/`.
32
+
33
+ ## [1.0.0-rc.1] — 2026-10-30
34
+
35
+ The first candidate of the 1.0 line, for teams of several repositories and roles: a change in three layers with their owners, a workspace of repositories with stories across them and its own live view, approvals per layer and harness packages per role, the backlog following the phases, the two gaps measured, principles, drift and API contracts checked, and `specgate migrate 1.0` to move a 0.27 project. Published under the npm dist-tag `next`; `latest` stays 0.27. See [Migrating to 1.0](docs/migrating-to-1.0.md).
36
+
37
+ ### Breaking
38
+
39
+ - **`depends=<repo>:REQ-NNN` is a dependency on another repository** (REQ-294): inside a repository it is no longer an `unknown_dependency`; the workspace judges it.
40
+ - **A change has three layers, each with its owner** (REQ-282..REQ-289, ADR-0042): `change new`, `propose`, `propose --from-brief` and `--from-ticket` write `discover.md` (product: context, use case, acceptance criteria `- AC-1: …`), `design.md` (architect) and `tasks.md` (developer) under the new `layered` schema, and no longer write `proposal.md`. `change new --schema spec-driven` keeps the 0.27 layout. A change written with 0.27 stays valid: its `proposal.md` is read as its Discover, and a missing `design.md` is a warning naming `specgate migrate 1.0`.
41
+ - **`depends=<repo>:REQ-NNN` is a dependency on another repository** (REQ-294): inside a repository it is no longer an `unknown_dependency`; the workspace judges it.
42
+ - **A 1.0 change is built once it is approved** (REQ-315, ADR-0043): `harness run` skips a requirement of an open change of the `layered` schema until every written layer has its approvals, with `harness_not_approved`. `approvals: { required: never }` in `harness.config.yaml` keeps the 0.27 behaviour, `always` asks it of 0.27 changes too.
43
+ - **A change has three layers, each with its owner** (REQ-282..REQ-289, ADR-0042): `change new`, `propose`, `propose --from-brief` and `--from-ticket` write `discover.md` (product: context, use case, acceptance criteria `- AC-1: …`), `design.md` (architect) and `tasks.md` (developer) under the new `layered` schema, and no longer write `proposal.md`. `change new --schema spec-driven` keeps the 0.27 layout. A change written with 0.27 stays valid: its `proposal.md` is read as its Discover, and a missing `design.md` is a warning naming `specgate migrate 1.0`.
44
+
45
+ ### Added
46
+
47
+ - **`specgate migrate 1.0`** (REQ-325..REQ-329): on a committed 0.27 project, moves each open change into the three layers — `proposal.md` to `discover.md` word for word, a `design.md` drafted from it, `tasks.md` kept, `schema: layered` — never touching the archive, and writes `docs/specs/migration-1.0.md` with what it did and what is left to a person. `--dry-run` changes nothing and prints the same report; a second run does nothing; a dirty tree is refused. `specgate status` names the command while an open change is still written for 0.27. Every breaking change, and how to stay on 0.27: [Migrating to 1.0](docs/migrating-to-1.0.md).
48
+ - **The workspace, live** (REQ-320..REQ-324): `specgate studio` in a workspace repository serves its `/live` — a panel per repository with the requirement in flight, its gate step and the agent's last step; each story with its parts open, building, archived or missing, and the parts waiting on another repository's undelivered requirement named; the cost per repository and per story. `specgate workspace harness run --story <id>` starts every repository's harness for its part at once, and none when one repository's base already fails the gate. `workspace live --json` prints the model.
49
+ - **The organization's principles** (REQ-330..REQ-332): `docs/specs/principles.md` with `principles_version` and `source` (a principles file, or `pack:<pack_id>` when a domain pack delivers it). `specgate check` names the version and warns `principles_behind` when the source has a newer one; a principle with `run=` or `forbid=` … `in=` is checked by the gate (`principle_failed`; `level=warn` for advice).
50
+ - **Behaviour changed without a specification** (REQ-333, REQ-334): `check` warns `spec_drift` for each commit since the base that changes production code and names no requirement in its message or in a test it changes; `specgate drift` reports it for CI, and `drift: gate` in harness.config.yaml fails both.
51
+ - **A requirement points at an API operation** (REQ-335, REQ-336): `api=` in its trace comment, a JSON pointer or an operationId of an OpenAPI or AsyncAPI contract; `validate` fails when it does not resolve, and `specgate contract check` lists the operations no requirement cites.
52
+ - **`specgate adopt --scope <path>`** (REQ-337, REQ-338): specify one area of a large repository; adoption warns `adopt_too_big` when it finds more areas than the eight it proposes.
53
+ - **The two gaps** (REQ-300..REQ-304): `harness report` says, for each requirement that needed rework, whether the spec was clear and the code diverged (spec → implementation) or the spec missed part of the use case (intent → spec). It suggests one from what it recorded — a rejection the agent fixed, naming the check that caught it; an answered question, a review note or a modified delta — and a person sets it with `--classify REQ-NNN --gap spec-impl|intent-spec --reason "..."`. The report shows both shares overall and per module and what to strengthen; the studio's report shows both shares. The run ledger now records the gate stage that rejected each attempt.
54
+ - **The board follows the change** (REQ-305): `alm sync` moves each change's ticket — its `ticket:` or `origin: alm:<key>` — to the state of its phase: `proposed`, `building` once a requirement has a `harness/REQ-NNN` branch or is Implemented, `archived`. Each provider has a default (Jira status category, Azure Boards state, a GitHub label or a closed issue, a Linear state type) and `phases:` in `alm.config.yaml` names a board's own. A move already made is not made again (`.specops/alm-phases.json`), and `--dry-run` prints each move. `mirror: false` keeps `alm sync` from creating an issue per requirement where the tickets are the source.
55
+ - **What the ticket gains comes back as notes** (REQ-306): `alm pull` brings each open change's new comments, and a note when the ticket's description changed, into its `review.md` — never into the proposal or the delta.
56
+ - **One sub-issue per repository** (REQ-307): a change that lists `repositories:` gets a sub-issue of its ticket for each, created once.
57
+ - **Linear** (REQ-308), a fourth built-in provider through its GraphQL API: `team_key` and `token_env`.
58
+ - **`alm phase` and `alm move`** (REQ-309): where a change stands, without the network, and its ticket moved there now; both are MCP tools (`specgate_alm_phase`, `specgate_alm_move`). New provider capabilities (`phase`, `subIssues`, `comments`, `readText`) are optional, so a community provider written for 0.27 still loads.
59
+ - **A workspace of repositories** (REQ-290..298, ADR-0041): `specgate.workspace.yaml` names the repositories of one product, and `specgate workspace init|status|check|propose` say where the product stands, run every repository's gate at once, resolve ids qualified with their repository (`tracking:REQ-012`), hold a requirement back until the one it depends on in another repository is delivered, and split a business story into a change in each repository it touches — done when every one is archived. See [docs/workspace.md](docs/workspace.md).
60
+ - **Roles and an approval per layer** (REQ-310..REQ-315, REQ-319, ADR-0043): `roles:` in `harness.config.yaml` names who holds each role; `change approve <id> --layer discover|design|tasks` records an approval by the layer's owner — product, architect, developer — with `approvals.quorum` for how many people a layer needs. Each approval goes in the change's `approvals.json` with a hash of the layer, kept in the archive; editing an approved layer asks for its approval again. The studio's Review shows each layer's approvals and approves the layer the viewer owns.
61
+ - **Role packages** (REQ-316..REQ-318, ADR-0043): `harness/<role>.md`, with `applies-to` by kind, capability or tag, reaches the prompt of the requirements it concerns under "Role constraints"; the prompt event, the run record, the live view and `harness report` name each package as `role@version`, and the review check gives one line of advice per package.
62
+ - **The layer rules** (REQ-284..REQ-287): `change validate` warns when the Discover names a technology, when the design does not answer an acceptance criterion, when a task names no requirement, and when the Discover differs from the version it was agreed at. `change validate --strict` fails on them.
63
+ - **The layers are named where people look** (REQ-288, REQ-289): `change status` lists discover, specs, design and tasks with their owner ("Write discover.md (product)"), the drafting guides say which layer the agent writes, and the studio's review lists each layer with its owner and whether it is written.
64
+
65
+ ### Fixed
66
+
67
+ - **A release candidate is never installed by default** (REQ-281): every publish workflow treated a pushed `v*` tag as `latest`, so a `v1.0.0-rc.1` would have become the default `npm install` and the `latest` image. A version with a prerelease part is now published under the dist-tag `next`, and its image is tagged with its version only.
68
+
12
69
  ## [0.27.9] — 2026-10-29
13
70
 
14
71
  After the seventh truck-flote run: the harness checks Docker before it starts, the report names scenarios as the spec does, a date is one value, and the kept agent is seen live.
@@ -37,6 +37,7 @@ exports.ValidateChangeUseCase = void 0;
37
37
  const path = __importStar(require("node:path"));
38
38
  const DeltaSpec_1 = require("../domain/DeltaSpec");
39
39
  const Diagnostic_1 = require("../domain/Diagnostic");
40
+ const ChangeLayers_1 = require("../domain/ChangeLayers");
40
41
  /**
41
42
  * Is this change well-formed?
42
43
  *
@@ -55,10 +56,25 @@ class ValidateChangeUseCase {
55
56
  const paths = this.projectRepo.getPaths();
56
57
  const diagnostics = [];
57
58
  const config = this.projectRepo.readConfig(changeId);
58
- if (this.projectRepo.readFile(path.join(paths.change(changeId), "proposal.md")) === null) {
59
- diagnostics.push((0, Diagnostic_1.error)("missing_proposal", `Change "${changeId}" has no proposal.md.`, {
59
+ // ADR-0042: the Discover is discover.md; a 0.27 change's is its proposal.
60
+ const dir = paths.change(changeId);
61
+ const read = (f) => this.projectRepo.readFile(path.join(dir, f));
62
+ const discoverFile = read("discover.md") !== null ? "discover.md" : ChangeLayers_1.LEGACY_DISCOVER;
63
+ const discover = read(discoverFile);
64
+ if (discover === null) {
65
+ const wanted = config.schema === ChangeLayers_1.LAYERED_SCHEMA ? "discover.md" : ChangeLayers_1.LEGACY_DISCOVER;
66
+ diagnostics.push((0, Diagnostic_1.error)("missing_proposal", `Change "${changeId}" has no ${wanted}.`, {
60
67
  target: changeId,
61
- fix: `Create ${path.join("docs/specs/changes", changeId, "proposal.md")}.`,
68
+ fix: `Create ${path.join("docs/specs/changes", changeId, wanted)}.`,
69
+ }));
70
+ }
71
+ else {
72
+ diagnostics.push(...(0, ChangeLayers_1.checkLayers)({
73
+ changeId,
74
+ discover,
75
+ discoverFile,
76
+ design: read("design.md"),
77
+ tasks: read("tasks.md"),
62
78
  }));
63
79
  }
64
80
  const deltas = this.projectRepo.listDeltas(changeId);
@@ -1 +1 @@
1
- {"version":3,"file":"ValidateChangeUseCase.js","sourceRoot":"","sources":["../../../../../packages/core/src/application/ValidateChangeUseCase.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,gDAAkC;AAElC,mDAAgD;AAChD,qDAAkE;AAclE;;;;;;;;GAQG;AACH,MAAa,qBAAqB;IACZ;IAApB,YAAoB,WAA+B;QAA/B,gBAAW,GAAX,WAAW,CAAoB;IAAG,CAAC;IAEhD,OAAO,CAAC,QAAgB,EAAE,OAA8B,EAAE;QAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC;QAC1C,MAAM,WAAW,GAAiB,EAAE,CAAC;QACrC,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QAErD,IAAI,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACzF,WAAW,CAAC,IAAI,CACd,IAAA,kBAAK,EAAC,kBAAkB,EAAE,WAAW,QAAQ,uBAAuB,EAAE;gBACpE,MAAM,EAAE,QAAQ;gBAChB,GAAG,EAAE,UAAU,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE,QAAQ,EAAE,aAAa,CAAC,GAAG;aAC3E,CAAC,CACH,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QACrD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YAC9C,WAAW,CAAC,IAAI,CACd,IAAA,oBAAO,EAAC,WAAW,EAAE,WAAW,QAAQ,2BAA2B,EAAE;gBACnE,MAAM,EAAE,QAAQ;gBAChB,GAAG,EAAE,wEAAwE;aAC9E,CAAC,CACH,CAAC;QACJ,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC1D,IAAI,WAAW,KAAK,IAAI;gBAAE,SAAS;YACnC,MAAM,EAAE,WAAW,EAAE,CAAC,EAAE,GAAG,qBAAS,CAAC,QAAQ,CAAC,WAAW,EAAE;gBACzD,UAAU,EAAE,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;gBAC7E,IAAI,EAAE,KAAK,CAAC,QAAQ;gBACpB,MAAM,EAAE,IAAI,CAAC,MAAM;aACpB,CAAC,CAAC;YACH,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACzB,CAAC;QAED,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC;IAC9D,CAAC;CACF;AAxCD,sDAwCC"}
1
+ {"version":3,"file":"ValidateChangeUseCase.js","sourceRoot":"","sources":["../../../../../packages/core/src/application/ValidateChangeUseCase.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ /**
3
+ * The operations of an interface contract, and how a requirement's `api=`
4
+ * reference resolves against them (REQ-335, REQ-336).
5
+ *
6
+ * OpenAPI (`paths:` → path → method, with its operationId) and AsyncAPI (2.x
7
+ * `channels:` → channel → publish/subscribe; 3.x `operations:`), in JSON or in
8
+ * YAML. The YAML is read by indentation for the few keys that matter, not by a
9
+ * full YAML parser: a contract is many things, and only its operations are ours.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.pointerSegment = pointerSegment;
13
+ exports.contractKind = contractKind;
14
+ exports.contractOperations = contractOperations;
15
+ exports.parseApiRef = parseApiRef;
16
+ exports.resolveApiRef = resolveApiRef;
17
+ const METHODS = new Set(["get", "put", "post", "delete", "patch", "options", "head", "trace"]);
18
+ const ASYNC_OPS = new Set(["publish", "subscribe"]);
19
+ /** A pointer segment: `~` and `/` escaped as RFC 6901 says. */
20
+ function pointerSegment(key) {
21
+ return key.replace(/~/g, "~0").replace(/\//g, "~1");
22
+ }
23
+ /** `openapi`, `asyncapi`, or null when the text is neither. */
24
+ function contractKind(text) {
25
+ if (/^\s*\{/.test(text)) {
26
+ try {
27
+ const doc = JSON.parse(text);
28
+ return doc.openapi || doc.swagger ? "openapi" : doc.asyncapi ? "asyncapi" : null;
29
+ }
30
+ catch {
31
+ return null;
32
+ }
33
+ }
34
+ if (/^(openapi|swagger)\s*:/m.test(text))
35
+ return "openapi";
36
+ if (/^asyncapi\s*:/m.test(text))
37
+ return "asyncapi";
38
+ return null;
39
+ }
40
+ /** The mapping keys of a YAML document as a tree, by indentation. */
41
+ function yamlTree(text) {
42
+ const root = { key: "", value: "", indent: -1, children: [] };
43
+ const stack = [root];
44
+ for (const raw of text.split(/\r?\n/)) {
45
+ if (!raw.trim() || /^\s*#/.test(raw) || /^\s*-/.test(raw))
46
+ continue;
47
+ const m = /^(\s*)("(?:[^"\\]|\\.)*"|'[^']*'|[^:#][^:]*?)\s*:(?:\s+(.*))?$/.exec(raw);
48
+ if (!m)
49
+ continue;
50
+ const indent = m[1].length;
51
+ const key = m[2].replace(/^"(.*)"$|^'(.*)'$/, (_s, a, b) => (a !== undefined ? a : b));
52
+ const node = {
53
+ key,
54
+ value: (m[3] || "").trim().replace(/^["']|["']$/g, ""),
55
+ indent,
56
+ children: [],
57
+ };
58
+ while (stack.length > 1 && stack[stack.length - 1].indent >= indent)
59
+ stack.pop();
60
+ stack[stack.length - 1].children.push(node);
61
+ stack.push(node);
62
+ }
63
+ return root.children;
64
+ }
65
+ function toTree(nodes) {
66
+ const out = {};
67
+ for (const n of nodes)
68
+ out[n.key] = n.children.length ? toTree(n.children) : n.value;
69
+ return out;
70
+ }
71
+ function asTree(v) {
72
+ return v && typeof v === "object" ? v : {};
73
+ }
74
+ /** Every operation the contract declares, in document order. */
75
+ function contractOperations(text, _file = "") {
76
+ const kind = contractKind(text);
77
+ if (!kind)
78
+ return [];
79
+ let doc;
80
+ if (/^\s*\{/.test(text)) {
81
+ try {
82
+ doc = JSON.parse(text);
83
+ }
84
+ catch {
85
+ return [];
86
+ }
87
+ }
88
+ else {
89
+ doc = toTree(yamlTree(text));
90
+ }
91
+ const ops = [];
92
+ const id = (v) => typeof asTree(v).operationId === "string" ? String(asTree(v).operationId) : "";
93
+ if (kind === "openapi") {
94
+ for (const [p, item] of Object.entries(asTree(doc.paths))) {
95
+ for (const [method, op] of Object.entries(asTree(item))) {
96
+ if (!METHODS.has(method.toLowerCase()))
97
+ continue;
98
+ ops.push({
99
+ kind,
100
+ pointer: `/paths/${pointerSegment(p)}/${method}`,
101
+ label: `${method.toUpperCase()} ${p}`,
102
+ operationId: id(op),
103
+ });
104
+ }
105
+ }
106
+ return ops;
107
+ }
108
+ for (const [channel, item] of Object.entries(asTree(doc.channels))) {
109
+ for (const [verb, op] of Object.entries(asTree(item))) {
110
+ if (!ASYNC_OPS.has(verb))
111
+ continue;
112
+ ops.push({
113
+ kind,
114
+ pointer: `/channels/${pointerSegment(channel)}/${verb}`,
115
+ label: `${verb} ${channel}`,
116
+ operationId: id(op),
117
+ });
118
+ }
119
+ }
120
+ for (const [key, op] of Object.entries(asTree(doc.operations))) {
121
+ ops.push({
122
+ kind,
123
+ pointer: `/operations/${pointerSegment(key)}`,
124
+ label: key,
125
+ operationId: id(op) || key,
126
+ });
127
+ }
128
+ return ops;
129
+ }
130
+ function parseApiRef(ref) {
131
+ const hash = ref.indexOf("#");
132
+ return hash < 0
133
+ ? { file: "", target: ref.trim() }
134
+ : { file: ref.slice(0, hash).trim(), target: ref.slice(hash + 1).trim() };
135
+ }
136
+ /** Resolve `ref` against the contracts, keyed by their project-relative path. */
137
+ function resolveApiRef(ref, contracts) {
138
+ const { file, target } = parseApiRef(ref);
139
+ const matches = (op) => target.startsWith("/") ? op.pointer === target : op.operationId === target;
140
+ if (file) {
141
+ const norm = file.replace(/\\/g, "/").replace(/^\.\//, "");
142
+ const ops = contracts[norm];
143
+ if (!ops)
144
+ return { ok: false, reason: "contract_missing", file: norm };
145
+ const op = ops.find(matches);
146
+ return op
147
+ ? { ok: true, file: norm, operation: op }
148
+ : { ok: false, reason: "operation_missing", file: norm };
149
+ }
150
+ for (const [f, ops] of Object.entries(contracts)) {
151
+ const op = ops.find(matches);
152
+ if (op)
153
+ return { ok: true, file: f, operation: op };
154
+ }
155
+ return { ok: false, reason: "operation_missing", file: "" };
156
+ }
157
+ //# sourceMappingURL=ApiContract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ApiContract.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/ApiContract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ /**
3
+ * Roles and approvals per layer (REQ-310..315, ADR-0043).
4
+ *
5
+ * Pure: who holds a role, what an approval of a layer is worth against the
6
+ * layer's current text, and whether a change is approved. Reading the config,
7
+ * the git identity and `approvals.json` is the caller's.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.readApprovalSettings = readApprovalSettings;
11
+ exports.holdsRole = holdsRole;
12
+ exports.rolesHeld = rolesHeld;
13
+ exports.contentHash = contentHash;
14
+ exports.layerApprovals = layerApprovals;
15
+ exports.approvalsRequired = approvalsRequired;
16
+ exports.changeApproved = changeApproved;
17
+ const node_crypto_1 = require("node:crypto");
18
+ const ChangeLayers_1 = require("./ChangeLayers");
19
+ const REQUIRED = ["layered", "always", "never"];
20
+ const asList = (v) => Array.isArray(v)
21
+ ? v.map((x) => String(x).trim()).filter(Boolean)
22
+ : v === undefined || v === null || v === ""
23
+ ? []
24
+ : [String(v).trim()];
25
+ /**
26
+ * The settings from the parsed `roles:` and `approvals:` of
27
+ * `harness.config.yaml`. Throws on a value it cannot read, naming it.
28
+ */
29
+ function readApprovalSettings(parsed) {
30
+ const roles = {};
31
+ const rawRoles = parsed.roles;
32
+ if (rawRoles !== undefined && rawRoles !== null) {
33
+ if (typeof rawRoles !== "object" || Array.isArray(rawRoles)) {
34
+ throw new Error("roles: must map each role to the people who hold it");
35
+ }
36
+ for (const [role, who] of Object.entries(rawRoles)) {
37
+ roles[role.trim()] = asList(who);
38
+ }
39
+ }
40
+ const raw = (parsed.approvals || {});
41
+ if (typeof raw !== "object" || Array.isArray(raw)) {
42
+ throw new Error("approvals: must be a mapping (required, quorum)");
43
+ }
44
+ const required = String(raw.required ?? "layered").trim();
45
+ if (!REQUIRED.includes(required)) {
46
+ throw new Error(`approvals.required: '${required}' is not one of ${REQUIRED.join(", ")}`);
47
+ }
48
+ const quorum = {};
49
+ for (const [layer, n] of Object.entries((raw.quorum || {}))) {
50
+ if (!ChangeLayers_1.LAYERS.some((l) => l.id === layer)) {
51
+ throw new Error(`approvals.quorum.${layer}: not a layer (discover, design, tasks)`);
52
+ }
53
+ const count = Number(n);
54
+ if (!Number.isInteger(count) || count < 1) {
55
+ throw new Error(`approvals.quorum.${layer}: '${n}' is not a whole number of people`);
56
+ }
57
+ quorum[layer] = count;
58
+ }
59
+ return { roles, required, quorum };
60
+ }
61
+ const norm = (s) => s.trim().toLowerCase();
62
+ /** Whether the person is one of a role's entries: their email or their name. */
63
+ function named(entries, who) {
64
+ const mine = [who.email, who.name].filter(Boolean).map(norm);
65
+ return entries.some((e) => mine.includes(norm(e)));
66
+ }
67
+ /** Whether the person holds the role. A role nobody is named for is open to anyone. */
68
+ function holdsRole(settings, role, who) {
69
+ const entries = settings.roles[role];
70
+ if (!entries || entries.length === 0)
71
+ return true;
72
+ return named(entries, who);
73
+ }
74
+ /** Every role the person holds: the ones naming them, and the ones naming nobody. */
75
+ function rolesHeld(settings, who) {
76
+ const roles = new Set([...ChangeLayers_1.LAYERS.map((l) => l.owner), ...Object.keys(settings.roles)]);
77
+ return [...roles].filter((r) => holdsRole(settings, r, who)).sort();
78
+ }
79
+ /** The hash an approval is of: line endings do not count, the text does. */
80
+ function contentHash(text) {
81
+ return `sha256:${(0, node_crypto_1.createHash)("sha256").update(text.replace(/\r\n/g, "\n")).digest("hex")}`;
82
+ }
83
+ const personKey = (who) => norm(who.email || who.name);
84
+ /** What each layer's approvals are worth against its current text. */
85
+ function layerApprovals(layers, records, settings) {
86
+ return layers.map((l) => {
87
+ const hash = l.text === null ? "" : contentHash(l.text);
88
+ const mine = records.filter((r) => r.layer === l.id);
89
+ const current = [];
90
+ const seen = new Set();
91
+ for (const r of mine) {
92
+ if (r.hash !== hash)
93
+ continue;
94
+ const key = personKey(r.by);
95
+ if (seen.has(key))
96
+ continue;
97
+ seen.add(key);
98
+ current.push(r);
99
+ }
100
+ const stale = mine.filter((r) => r.hash !== hash);
101
+ const quorum = settings.quorum[l.id] || 1;
102
+ return {
103
+ layer: l.id,
104
+ owner: l.owner,
105
+ file: l.file,
106
+ present: l.text !== null,
107
+ quorum,
108
+ approvals: current,
109
+ stale,
110
+ approved: l.text !== null && current.length >= quorum,
111
+ };
112
+ });
113
+ }
114
+ /** Whether a change of this schema needs its approvals before it is built (REQ-315). */
115
+ function approvalsRequired(schema, settings) {
116
+ if (settings.required === "never")
117
+ return false;
118
+ if (settings.required === "always")
119
+ return true;
120
+ return schema === "layered";
121
+ }
122
+ /** A change is approved when every written layer is, and at least one is written. */
123
+ function changeApproved(layers) {
124
+ const present = layers.filter((l) => l.present);
125
+ return present.length > 0 && present.every((l) => l.approved);
126
+ }
127
+ //# sourceMappingURL=Approvals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Approvals.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/Approvals.ts"],"names":[],"mappings":"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"}