@sdd-method/maintain-cli 0.5.0 → 0.7.0
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.
- package/dist/lib/behaviour-store/archive.js +180 -0
- package/dist/lib/catalogue/build.js +18 -1
- package/dist/lib/catalogue/builders/capabilities.js +4 -1
- package/dist/lib/catalogue/builders/events.js +12 -5
- package/dist/lib/catalogue/builders/product-edges.js +3 -1
- package/dist/lib/catalogue/builders/product-platform-dependencies.js +16 -1
- package/dist/lib/catalogue/builders/repo-edges.js +4 -3
- package/dist/lib/catalogue/builders/repositories.js +6 -0
- package/dist/lib/catalogue/builders/requirements.js +208 -0
- package/dist/lib/catalogue/builders/service-contract.js +6 -9
- package/dist/lib/catalogue/canonical-schema.yaml +160 -7
- package/dist/lib/catalogue/rubric-coverage-map.yaml +36 -0
- package/dist/lib/catalogue/schema-compat.js +1 -1
- package/dist/lib/validate/behaviour-store.js +112 -1
- package/dist/lib/vocabulary/renames.js +40 -0
- package/dist/lib/vocabulary/vocabulary-renames.yaml +76 -0
- package/dist/maintain-version.json +2 -2
- package/dist/verify-version.json +2 -2
- package/package.json +2 -2
- package/scripts/validate-app-dependencies.sh +29 -21
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# Emitted by sdd-cli's YAML emitter with no line folding; the three header scalars are quoted for the
|
|
8
8
|
# textual readers downstream. The one deliberate difference from the authored form is schema_status:
|
|
9
9
|
# the authored form says canonical; a projection never does.
|
|
10
|
-
schema_version: "
|
|
10
|
+
schema_version: "4.0.0"
|
|
11
11
|
schema_owner: "sdd-method"
|
|
12
12
|
schema_status: "generated"
|
|
13
13
|
enums:
|
|
@@ -389,7 +389,7 @@ enums:
|
|
|
389
389
|
description: M-ADR 0159 entity persistence kind. Bound at 3.13.0 from the column descriptions; measured against both reference estates before binding (M-ADR 0205 §4.4).
|
|
390
390
|
required_stability:
|
|
391
391
|
values:
|
|
392
|
-
- value:
|
|
392
|
+
- value: stable
|
|
393
393
|
- value: provisional
|
|
394
394
|
- value: unknown
|
|
395
395
|
description: "M-ADR 0204: the stability a consumer requires of a target; external | legacy live in target_locus, not here. Bound at 3.13.0 from the column descriptions; measured against both reference estates before binding (M-ADR 0205 §4.4)."
|
|
@@ -613,6 +613,35 @@ enums:
|
|
|
613
613
|
- value: generated-from
|
|
614
614
|
description: Its content was produced from the other repository's templates.
|
|
615
615
|
description: How one repository depends on another's code (M-ADR 0217 §4.1).
|
|
616
|
+
requirement_stage:
|
|
617
|
+
values:
|
|
618
|
+
- value: living
|
|
619
|
+
description: "In the living spec: shipped behaviour."
|
|
620
|
+
- value: agreed
|
|
621
|
+
description: Added by an open change whose agreement record is agreed; not yet shipped.
|
|
622
|
+
description: Where a catalogued requirement stands (M-ADR 0221 §4.4).
|
|
623
|
+
requirement_change_kind:
|
|
624
|
+
values:
|
|
625
|
+
- value: baseline
|
|
626
|
+
description: Recorded existing behaviour through the baseline gate.
|
|
627
|
+
- value: change
|
|
628
|
+
description: A delta agreed, built and judged through the change gate.
|
|
629
|
+
description: What kind of archived change a lineage row names (M-ADR 0222 §4.3).
|
|
630
|
+
requirement_operation:
|
|
631
|
+
values:
|
|
632
|
+
- value: ADDED
|
|
633
|
+
- value: MODIFIED
|
|
634
|
+
- value: RENAMED
|
|
635
|
+
description: How a change's delta treated a requirement that is current now (a removed requirement has no row).
|
|
636
|
+
requirement_status_value:
|
|
637
|
+
values:
|
|
638
|
+
- value: agreed
|
|
639
|
+
description: In an agreed, open change.
|
|
640
|
+
- value: declared-shipped
|
|
641
|
+
description: Its change archived on a passing judge, with this repository's task group complete.
|
|
642
|
+
- value: observed
|
|
643
|
+
description: Independent evidence from this repository's code matches (a baseline's characterization tests or a reverse snapshot).
|
|
644
|
+
description: A requirement's status in one implementing repository (M-ADR 0221 §4.5).
|
|
616
645
|
catalogues:
|
|
617
646
|
domains:
|
|
618
647
|
primary_key: id
|
|
@@ -1276,7 +1305,7 @@ catalogues:
|
|
|
1276
1305
|
- name: contract_provenance
|
|
1277
1306
|
type: string
|
|
1278
1307
|
nullable: true
|
|
1279
|
-
description: "How this capability's declared contract SET was produced, from `traceability.contracts.provenance`. hand-authored | reverse-extracted | generated — the vocabulary the method already ships for `authoring-mode`, reused deliberately so provenance has one vocabulary and not two. Empty means undeclared, and undeclared reads as `hand-authored` by convention. Added at 3.5.0. ⚠ STRICTLY ORTHOGONAL to the contract-set maturity ladder (`traceability.contracts.status`: planned | provisional | partial | complete |
|
|
1308
|
+
description: "How this capability's declared contract SET was produced, from `traceability.contracts.provenance`. hand-authored | reverse-extracted | generated — the vocabulary the method already ships for `authoring-mode`, reused deliberately so provenance has one vocabulary and not two. Empty means undeclared, and undeclared reads as `hand-authored` by convention. Added at 3.5.0. ⚠ STRICTLY ORTHOGONAL to the contract-set maturity ladder (`traceability.contracts.status`: planned | provisional | partial | complete | stable | none): provenance never constrains status and status never constrains provenance, and `reverse-extracted` + `complete` is the legitimate pair that motivated the field. Evidence: 2 reverse-estate manifests had written `reverse-extracted` INTO contracts.status, where it is invalid, because a complete reverse-derived contract surface had no way to say both true things at once and the author picked the provenance one. A rule here that constrained maturity would re-create exactly that defect. ⚠ Note the asymmetry this column exposes and does not fix: the maturity ladder it is orthogonal to has NO catalogue column at all — `traceability.contracts.status` is validator-enforced but uncatalogued, so it is queryable by no consumer. That is the authored-but-uncatalogued class (a block with no column has no automated reader, so nothing can notice when it is absent or wrong); this column is given one precisely so the provenance half does not join it. Giving the ladder a column is a candidate follow-on, not done here."
|
|
1280
1309
|
- name: source
|
|
1281
1310
|
type: string
|
|
1282
1311
|
nullable: false
|
|
@@ -1523,7 +1552,7 @@ catalogues:
|
|
|
1523
1552
|
- name: kind
|
|
1524
1553
|
type: string
|
|
1525
1554
|
nullable: false
|
|
1526
|
-
description: rest | graphql | rpc | event | authz. rpc covers Proto/gRPC; event covers Avro schemas; authz covers authorization/policy schemas (SpiceDB .zed and equivalents), added because an adopter contract
|
|
1555
|
+
description: rest | graphql | rpc | event | authz. rpc covers Proto/gRPC; event covers Avro schemas; authz covers authorization/policy schemas (SpiceDB .zed and equivalents), added because an adopter contract agreement gate certified .zed schemas as contracts while no kind could represent them.
|
|
1527
1556
|
- name: version
|
|
1528
1557
|
type: string
|
|
1529
1558
|
nullable: true
|
|
@@ -1888,7 +1917,7 @@ catalogues:
|
|
|
1888
1917
|
- name: version
|
|
1889
1918
|
type: string
|
|
1890
1919
|
nullable: true
|
|
1891
|
-
description: Optional explicit version; null until the domain model is formally versioned (typically
|
|
1920
|
+
description: Optional explicit version; null until the domain model is formally versioned (typically when a contract is first pinned).
|
|
1892
1921
|
- name: evidence_class
|
|
1893
1922
|
type: string
|
|
1894
1923
|
nullable: true
|
|
@@ -2142,7 +2171,7 @@ catalogues:
|
|
|
2142
2171
|
- name: required_stability
|
|
2143
2172
|
type: string
|
|
2144
2173
|
nullable: true
|
|
2145
|
-
description: "provisional |
|
|
2174
|
+
description: "provisional | stable | unknown. The consuming SDD's expectation of contract stability, carried verbatim from app-platform-dependencies.yaml. Not a bound enum: an out-of-vocabulary value is carried, not rejected, and is reported by validate-app-dependencies.sh."
|
|
2146
2175
|
description: "Edge: product depends on / consumes service. Forward sources from app-platform-dependencies.yaml. service_id is external because the consuming product (an application-product SDD) references a service owned by the platform SDD."
|
|
2147
2176
|
foreign_keys:
|
|
2148
2177
|
- column: product_id
|
|
@@ -2930,7 +2959,7 @@ catalogues:
|
|
|
2930
2959
|
- name: required_stability
|
|
2931
2960
|
type: string
|
|
2932
2961
|
nullable: true
|
|
2933
|
-
description: "provisional |
|
|
2962
|
+
description: "provisional | stable | unknown. Optional; the consuming SDD's expectation of contract stability — and only that: where the target lives and what lifecycle its implementation is in are separate concerns that do not belong in this field. Not a bound enum: an out-of-vocabulary value is carried, not rejected, and is reported by validate-app-dependencies.sh."
|
|
2934
2963
|
- name: contract_path
|
|
2935
2964
|
type: string
|
|
2936
2965
|
nullable: true
|
|
@@ -4115,3 +4144,127 @@ catalogues:
|
|
|
4115
4144
|
external: true
|
|
4116
4145
|
column_enums:
|
|
4117
4146
|
kind: enums.repo_dependency_kind
|
|
4147
|
+
requirements:
|
|
4148
|
+
primary_key: id
|
|
4149
|
+
columns:
|
|
4150
|
+
- name: id
|
|
4151
|
+
type: string
|
|
4152
|
+
nullable: false
|
|
4153
|
+
description: <feature_id>::<requirement name>. The name is unique within its spec and stable across changes (M-ADR 0221 §4.7).
|
|
4154
|
+
- name: feature_id
|
|
4155
|
+
type: string
|
|
4156
|
+
nullable: false
|
|
4157
|
+
description: "The catalogue feature whose living spec holds the requirement: the spec's directory name under openspec/specs/."
|
|
4158
|
+
- name: name
|
|
4159
|
+
type: string
|
|
4160
|
+
nullable: false
|
|
4161
|
+
description: The requirement's name as the spec heads it; automated tests carry it.
|
|
4162
|
+
- name: stage
|
|
4163
|
+
type: string
|
|
4164
|
+
nullable: false
|
|
4165
|
+
description: "Bound to requirement_stage: living (in the living spec) or agreed (added by an open change whose agreement record is agreed)."
|
|
4166
|
+
- name: spec_path
|
|
4167
|
+
type: string
|
|
4168
|
+
nullable: false
|
|
4169
|
+
description: "The file that holds the requirement: the living spec, or the open change's delta."
|
|
4170
|
+
- name: scenario_count
|
|
4171
|
+
type: integer
|
|
4172
|
+
nullable: false
|
|
4173
|
+
description: Scenarios under the requirement in that file.
|
|
4174
|
+
- name: source_sdd_id
|
|
4175
|
+
type: string
|
|
4176
|
+
nullable: true
|
|
4177
|
+
description: Originating SDD (stamped at aggregation; empty in a per-SDD build).
|
|
4178
|
+
- name: platform_sdd_id
|
|
4179
|
+
type: string
|
|
4180
|
+
nullable: true
|
|
4181
|
+
description: The platform SDD the row belongs to (stamped at aggregation).
|
|
4182
|
+
description: "A requirement in the SDD's behaviour store (M-ADR 0221): one row per requirement of a living spec, keyed by the catalogue feature the spec describes, and per requirement an open agreed change adds. Projected from openspec/ at the SDD's commit; never authored here."
|
|
4183
|
+
foreign_keys:
|
|
4184
|
+
- column: feature_id
|
|
4185
|
+
references: features.id
|
|
4186
|
+
nullable: false
|
|
4187
|
+
column_enums:
|
|
4188
|
+
stage: enums.requirement_stage
|
|
4189
|
+
requirement_lineage:
|
|
4190
|
+
primary_key: id
|
|
4191
|
+
columns:
|
|
4192
|
+
- name: id
|
|
4193
|
+
type: string
|
|
4194
|
+
nullable: false
|
|
4195
|
+
description: <requirement_id>__<change_id>.
|
|
4196
|
+
- name: requirement_id
|
|
4197
|
+
type: string
|
|
4198
|
+
nullable: false
|
|
4199
|
+
- name: change_id
|
|
4200
|
+
type: string
|
|
4201
|
+
nullable: false
|
|
4202
|
+
description: The archived change's directory name (its archive date prefix included).
|
|
4203
|
+
- name: change_kind
|
|
4204
|
+
type: string
|
|
4205
|
+
nullable: false
|
|
4206
|
+
description: "Bound to requirement_change_kind: baseline or change."
|
|
4207
|
+
- name: operation
|
|
4208
|
+
type: string
|
|
4209
|
+
nullable: false
|
|
4210
|
+
description: "Bound to requirement_operation: how the change's delta treated the requirement."
|
|
4211
|
+
- name: archived_on
|
|
4212
|
+
type: string
|
|
4213
|
+
nullable: false
|
|
4214
|
+
description: The archive date (YYYY-MM-DD) from the change's directory name.
|
|
4215
|
+
- name: source_sdd_id
|
|
4216
|
+
type: string
|
|
4217
|
+
nullable: true
|
|
4218
|
+
description: Originating SDD (stamped at aggregation; empty in a per-SDD build).
|
|
4219
|
+
- name: platform_sdd_id
|
|
4220
|
+
type: string
|
|
4221
|
+
nullable: true
|
|
4222
|
+
description: The platform SDD the row belongs to (stamped at aggregation).
|
|
4223
|
+
description: "Annotation: an archived change that made a current requirement what it is (M-ADR 0221 §4.6), in archive order. A baseline change (one carrying baseline-evidence.md) records existing behaviour; any other change carries a verdict."
|
|
4224
|
+
foreign_keys:
|
|
4225
|
+
- column: requirement_id
|
|
4226
|
+
references: requirements.id
|
|
4227
|
+
nullable: false
|
|
4228
|
+
column_enums:
|
|
4229
|
+
change_kind: enums.requirement_change_kind
|
|
4230
|
+
operation: enums.requirement_operation
|
|
4231
|
+
requirement_status:
|
|
4232
|
+
primary_key: id
|
|
4233
|
+
columns:
|
|
4234
|
+
- name: id
|
|
4235
|
+
type: string
|
|
4236
|
+
nullable: false
|
|
4237
|
+
description: <requirement_id>__<repo_id>.
|
|
4238
|
+
- name: requirement_id
|
|
4239
|
+
type: string
|
|
4240
|
+
nullable: false
|
|
4241
|
+
- name: repo_id
|
|
4242
|
+
type: string
|
|
4243
|
+
nullable: false
|
|
4244
|
+
description: "The implementing repository: a work order's repository, or a repository a baseline was observed in."
|
|
4245
|
+
- name: status
|
|
4246
|
+
type: string
|
|
4247
|
+
nullable: false
|
|
4248
|
+
description: Bound to requirement_status_value.
|
|
4249
|
+
- name: basis
|
|
4250
|
+
type: string
|
|
4251
|
+
nullable: false
|
|
4252
|
+
description: "The change that produced the status: the open change's or the archived change's directory name."
|
|
4253
|
+
- name: source_sdd_id
|
|
4254
|
+
type: string
|
|
4255
|
+
nullable: true
|
|
4256
|
+
description: Originating SDD (stamped at aggregation; empty in a per-SDD build).
|
|
4257
|
+
- name: platform_sdd_id
|
|
4258
|
+
type: string
|
|
4259
|
+
nullable: true
|
|
4260
|
+
description: The platform SDD the row belongs to (stamped at aggregation).
|
|
4261
|
+
description: "Edge: a requirement's status in a repository that implements it (M-ADR 0221 §4.5; M-ADR 0223 §4.7). Derived, never authored: agreed from an open agreed change's work orders, declared-shipped from an archived change's PASS verdict and its work orders, observed from a baseline's evidence. The latest event in archive order wins."
|
|
4262
|
+
foreign_keys:
|
|
4263
|
+
- column: requirement_id
|
|
4264
|
+
references: requirements.id
|
|
4265
|
+
nullable: false
|
|
4266
|
+
- column: repo_id
|
|
4267
|
+
references: repositories.id
|
|
4268
|
+
nullable: false
|
|
4269
|
+
column_enums:
|
|
4270
|
+
status: enums.requirement_status_value
|
|
@@ -869,3 +869,39 @@ coverage:
|
|
|
869
869
|
kind: enum
|
|
870
870
|
scoring: not-applicable
|
|
871
871
|
reason: closed vocabulary, not an artefact
|
|
872
|
+
requirements:
|
|
873
|
+
kind: entity
|
|
874
|
+
scoring: not-applicable
|
|
875
|
+
reason: projected from the behaviour store, whose baseline and change gates hold its quality (M-ADR 0222)
|
|
876
|
+
relationships:
|
|
877
|
+
feature_id:
|
|
878
|
+
class: membership
|
|
879
|
+
impact_flow: toward-target
|
|
880
|
+
requirement_lineage:
|
|
881
|
+
kind: annotation
|
|
882
|
+
scoring: derived-edge
|
|
883
|
+
class: lineage
|
|
884
|
+
impact_flow: none
|
|
885
|
+
reason: derived from the archived changes scored by their own judge
|
|
886
|
+
requirement_status:
|
|
887
|
+
kind: edge
|
|
888
|
+
scoring: derived-edge
|
|
889
|
+
class: realisation
|
|
890
|
+
impact_flow: both
|
|
891
|
+
reason: derived from agreement records, verdicts and baseline evidence
|
|
892
|
+
requirement_stage:
|
|
893
|
+
kind: enum
|
|
894
|
+
scoring: not-applicable
|
|
895
|
+
reason: closed vocabulary, not an artefact
|
|
896
|
+
requirement_change_kind:
|
|
897
|
+
kind: enum
|
|
898
|
+
scoring: not-applicable
|
|
899
|
+
reason: closed vocabulary, not an artefact
|
|
900
|
+
requirement_operation:
|
|
901
|
+
kind: enum
|
|
902
|
+
scoring: not-applicable
|
|
903
|
+
reason: closed vocabulary, not an artefact
|
|
904
|
+
requirement_status_value:
|
|
905
|
+
kind: enum
|
|
906
|
+
scoring: not-applicable
|
|
907
|
+
reason: closed vocabulary, not an artefact
|
|
@@ -14,6 +14,16 @@
|
|
|
14
14
|
// With no such targets the check is vacuous, and says so.
|
|
15
15
|
// 4. verdicts: every archived change except a baseline has a verdict.md and a record whose
|
|
16
16
|
// verdict is PASS and names it (M-ADR 0223 §4.6).
|
|
17
|
+
// 5. surfaces: every archived baseline's evidence lists, under **Surfaces:**, each contract the
|
|
18
|
+
// repositories it was observed in serve, as covered at transport or excluded with a
|
|
19
|
+
// reason, and the list is checked against the catalogue (catalogues/repo_contract.csv),
|
|
20
|
+
// never against itself (M-ADR 0222 §4.3, §4.4). A list the catalogue gives nothing to
|
|
21
|
+
// check against (no Observed in line, or a repository with no catalogue contracts) fails:
|
|
22
|
+
// a cross-check of zero contracts is unresolved, not a pass.
|
|
23
|
+
// 6. scope: a baseline ADDS requirements; it REMOVES, MODIFIES or RENAMES only a requirement whose
|
|
24
|
+
// latest archived change was a baseline, which is how a corrective baseline withdraws
|
|
25
|
+
// what a baseline admitted wrongly. A requirement a judged change shipped is changed only
|
|
26
|
+
// by a change (M-ADR 0222 §4.4). Read in archive order.
|
|
17
27
|
//
|
|
18
28
|
// A sibling-repository store (M-ADR 0221 §4.2) is checked in that repository, not here.
|
|
19
29
|
//
|
|
@@ -25,6 +35,8 @@ import { access, readdir, readFile } from "node:fs/promises";
|
|
|
25
35
|
import { basename, join } from "node:path";
|
|
26
36
|
import { promisify } from "node:util";
|
|
27
37
|
import { parseDocument } from "yaml";
|
|
38
|
+
import { observedIn, orderArchives, parseRequirements, parseSurfaces, renamedFrom, surfaceMatches } from "../behaviour-store/archive.js";
|
|
39
|
+
import { parseCatalogueCsv } from "../catalogue/csv-reader.js";
|
|
28
40
|
const execFileAsync = promisify(execFile);
|
|
29
41
|
/** The OpenSpec release the method depends on (M-ADR 0223 ruling 2). The module installs exactly this. */
|
|
30
42
|
export const OPENSPEC_VERSION = "1.14.3";
|
|
@@ -271,6 +283,77 @@ export async function checkBehaviourStore(repo) {
|
|
|
271
283
|
failures.push(`${record.file}: verdict.record ${JSON.stringify(vr)} does not name an existing file`);
|
|
272
284
|
}
|
|
273
285
|
lines.push(`verdicts: ${archived.length} archived change(s), ${baselines} baseline(s), ${archived.length - baselines} needing a PASS verdict`);
|
|
286
|
+
// 5. surfaces, and 6. what each baseline may change, in archive order
|
|
287
|
+
const archiveRel = `${STORE_DIR}/changes/archive`;
|
|
288
|
+
const { order, ties } = await orderArchives(repo, archiveRel, archived);
|
|
289
|
+
const servedBy = baselines > 0 ? await catalogueContracts(repo) : null;
|
|
290
|
+
let surfacesChecked = 0;
|
|
291
|
+
const lastTouch = new Map();
|
|
292
|
+
let scopeChecked = 0;
|
|
293
|
+
for (const name of order) {
|
|
294
|
+
const dir = `${archiveRel}/${name}`;
|
|
295
|
+
const evidence = await readText(join(repo, dir, "baseline-evidence.md"));
|
|
296
|
+
const isBaseline = evidence !== null;
|
|
297
|
+
if (isBaseline) {
|
|
298
|
+
const parsed = parseSurfaces(evidence);
|
|
299
|
+
if (!parsed.present)
|
|
300
|
+
failures.push(`${dir}/baseline-evidence.md: no **Surfaces:** list; name every contract the observed repositories serve, covered at transport or excluded with a reason`);
|
|
301
|
+
for (const p of parsed.problems)
|
|
302
|
+
failures.push(`${dir}/baseline-evidence.md: Surfaces: ${p}`);
|
|
303
|
+
if (parsed.present) {
|
|
304
|
+
if (servedBy === null) {
|
|
305
|
+
failures.push(`${dir}/baseline-evidence.md: Surfaces cannot be cross-checked: catalogues/repo_contract.csv or catalogues/contracts.csv is missing; build the catalogue`);
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
const repos = observedIn(evidence);
|
|
309
|
+
if (repos.length === 0)
|
|
310
|
+
failures.push(`${dir}/baseline-evidence.md: Surfaces cannot be cross-checked: no **Observed in:** line names a repository`);
|
|
311
|
+
for (const r of repos) {
|
|
312
|
+
if ((servedBy.get(r) ?? []).length === 0) {
|
|
313
|
+
failures.push(`${dir}/baseline-evidence.md: Surfaces cannot be cross-checked: catalogues/repo_contract.csv records no contract for ${r}; declare the contracts it serves in its repo-config`);
|
|
314
|
+
}
|
|
315
|
+
for (const c of servedBy.get(r) ?? []) {
|
|
316
|
+
surfacesChecked++;
|
|
317
|
+
if (!parsed.entries.some((e) => surfaceMatches(e.contract, c.id, c.specPath))) {
|
|
318
|
+
failures.push(`${dir}/baseline-evidence.md: Surfaces does not list ${c.specPath || c.id} (${c.id}), which ${r} serves (catalogues/repo_contract.csv); list it as covered at transport or excluded with a reason`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
for (const feature of await dirs(join(repo, dir, "specs"))) {
|
|
326
|
+
const text = (await readText(join(repo, dir, "specs", feature, "spec.md"))) ?? "";
|
|
327
|
+
const targets = [
|
|
328
|
+
...parseRequirements(text).filter((r) => r.operation === "MODIFIED" || r.operation === "REMOVED").map((r) => r.name),
|
|
329
|
+
...renamedFrom(text),
|
|
330
|
+
];
|
|
331
|
+
for (const t of targets) {
|
|
332
|
+
const key = `${feature}::${t}`;
|
|
333
|
+
if (isBaseline) {
|
|
334
|
+
scopeChecked++;
|
|
335
|
+
const last = lastTouch.get(key);
|
|
336
|
+
if (last !== "baseline") {
|
|
337
|
+
failures.push(`${dir}: a baseline changes "${t}" (${feature}), which ${last === "change" ? "a judged change last shipped" : "no earlier archived change admitted"}; a baseline may remove or modify only what a baseline admitted, and anything else takes a change (M-ADR 0222 §4.4)`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
for (const r of parseRequirements(text)) {
|
|
342
|
+
const key = `${feature}::${r.name}`;
|
|
343
|
+
if (r.operation === "REMOVED")
|
|
344
|
+
lastTouch.delete(key);
|
|
345
|
+
else if (r.operation)
|
|
346
|
+
lastTouch.set(key, isBaseline ? "baseline" : "change");
|
|
347
|
+
}
|
|
348
|
+
for (const from of renamedFrom(text))
|
|
349
|
+
lastTouch.delete(`${feature}::${from}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
const unseen = servedBy === null ? "" : `; repositories with no catalogue contracts: ${[...new Set(await observedInAll(repo, archiveRel, order))].filter((r) => (servedBy.get(r) ?? []).length === 0).join(", ") || "none"}`;
|
|
353
|
+
lines.push(baselines === 0 ? "surfaces: vacuous (no baseline)" : `surfaces: ${baselines} baseline(s), ${surfacesChecked} served contract(s) checked against catalogues/repo_contract.csv${unseen}`);
|
|
354
|
+
lines.push(scopeChecked === 0 ? "scope: vacuous (no baseline removes, modifies or renames a requirement)" : `scope: ${scopeChecked} requirement(s) a baseline removes, modifies or renames, checked against their lineage`);
|
|
355
|
+
for (const [a, b] of ties)
|
|
356
|
+
lines.push(`order: ${a} and ${b} were archived the same day and the history does not order them; ordered by name`);
|
|
274
357
|
const population = recordFiles.length + open.length + archived.length;
|
|
275
358
|
const body = lines.map((l) => ` ${l}`).join("\n") + "\n";
|
|
276
359
|
if (failures.length > 0) {
|
|
@@ -278,7 +361,35 @@ export async function checkBehaviourStore(repo) {
|
|
|
278
361
|
}
|
|
279
362
|
if (population === 0)
|
|
280
363
|
return { exitCode: 0, stdout: body + `[N/A] the behaviour store has no changes and no agreement records yet\n` };
|
|
281
|
-
return { exitCode: 0, stdout: body + "[PASS] behaviour store: records, agreement, stale and
|
|
364
|
+
return { exitCode: 0, stdout: body + "[PASS] behaviour store: records, agreement, stale, verdict, surfaces and scope checks hold\n" };
|
|
365
|
+
}
|
|
366
|
+
/** Per repository, the contracts the catalogue says it serves (repo_contract ⋈ contracts.spec_path); null when either file is missing. */
|
|
367
|
+
async function catalogueContracts(repo) {
|
|
368
|
+
const rc = await readText(join(repo, "catalogues", "repo_contract.csv"));
|
|
369
|
+
const cs = await readText(join(repo, "catalogues", "contracts.csv"));
|
|
370
|
+
if (rc === null || cs === null)
|
|
371
|
+
return null;
|
|
372
|
+
const spec = new Map(parseCatalogueCsv(cs).rows.map((r) => [r["id"] ?? "", r["spec_path"] ?? ""]));
|
|
373
|
+
const out = new Map();
|
|
374
|
+
for (const r of parseCatalogueCsv(rc).rows) {
|
|
375
|
+
const repoId = r["repo_id"] ?? "";
|
|
376
|
+
const id = r["contract_id"] ?? "";
|
|
377
|
+
if (repoId === "" || id === "")
|
|
378
|
+
continue;
|
|
379
|
+
const list = out.get(repoId) ?? [];
|
|
380
|
+
list.push({ id, specPath: spec.get(id) ?? "" });
|
|
381
|
+
out.set(repoId, list);
|
|
382
|
+
}
|
|
383
|
+
return out;
|
|
384
|
+
}
|
|
385
|
+
async function observedInAll(repo, archiveRel, names) {
|
|
386
|
+
const out = [];
|
|
387
|
+
for (const n of names) {
|
|
388
|
+
const ev = await readText(join(repo, archiveRel, n, "baseline-evidence.md"));
|
|
389
|
+
if (ev !== null)
|
|
390
|
+
out.push(...observedIn(ev));
|
|
391
|
+
}
|
|
392
|
+
return out;
|
|
282
393
|
}
|
|
283
394
|
async function runOpenspec(repo, args) {
|
|
284
395
|
try {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The method's vocabulary renames (M-ADR 0222 §4.1), loaded from the vendored copy of
|
|
3
|
+
* sdd-method's docs/method/foundations/vocabulary-renames.yaml (one-way, sdd-method ->
|
|
4
|
+
* vendored; scripts/sync-vendored-script.sh, drift-tested in tests/parity).
|
|
5
|
+
*
|
|
6
|
+
* For one major release every reader accepts a former value and reads it as the new one;
|
|
7
|
+
* from `removed_in` the former value is an error. Readers ask this module rather than
|
|
8
|
+
* keeping their own copy, so a rename has one definition here as it has one in the bundles.
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { parse } from "yaml";
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
export const VENDORED_RENAMES_PATH = join(__dirname, "vocabulary-renames.yaml");
|
|
16
|
+
let cached;
|
|
17
|
+
export function vocabularyRenames() {
|
|
18
|
+
if (cached === undefined) {
|
|
19
|
+
const doc = parse(readFileSync(VENDORED_RENAMES_PATH, "utf8"));
|
|
20
|
+
cached = { introduced_in: String(doc.introduced_in), removed_in: String(doc.removed_in), renames: doc.renames };
|
|
21
|
+
}
|
|
22
|
+
return cached;
|
|
23
|
+
}
|
|
24
|
+
/** The rename with this id; throws on an id the file does not hold, so a reader cannot silently lose its alias. */
|
|
25
|
+
export function rename(id) {
|
|
26
|
+
const r = vocabularyRenames().renames.find((x) => x.id === id);
|
|
27
|
+
if (r === undefined)
|
|
28
|
+
throw new Error(`vocabulary-renames.yaml has no rename "${id}"`);
|
|
29
|
+
return r;
|
|
30
|
+
}
|
|
31
|
+
/** The value as a reader should treat it: the new name for the former one, anything else unchanged. */
|
|
32
|
+
export function canonicalValue(id, value) {
|
|
33
|
+
const r = rename(id);
|
|
34
|
+
return value === r.old ? r.new : value;
|
|
35
|
+
}
|
|
36
|
+
/** Whether a value is the former name under this rename (for the reader's finding). */
|
|
37
|
+
export function isFormerValue(id, value) {
|
|
38
|
+
return value === rename(id).old;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=renames.js.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Vocabulary renames (M-ADR 0222 §4.1)
|
|
2
|
+
#
|
|
3
|
+
# The tool-read values the method renamed when "lock" left its vocabulary, as data:
|
|
4
|
+
# every reader that accepts a former value, and the migrate verb, follows this file.
|
|
5
|
+
# For one major release a reader accepts the former value, treats it as the new one
|
|
6
|
+
# and reports it; from removed_in the former value is an error.
|
|
7
|
+
#
|
|
8
|
+
# Each reader that aliases a former value marks the place with a comment line
|
|
9
|
+
# vocabulary-alias: <old> -> <new>
|
|
10
|
+
# and the correspondence registry holds the readers and this file to each other in
|
|
11
|
+
# both directions (orchestration/correspondence-registry.yaml, vocabulary-renames).
|
|
12
|
+
#
|
|
13
|
+
# Consumed by:
|
|
14
|
+
# - validate-spec-lifecycle.sh, validate-traceability.sh, validate-app-dependencies.sh,
|
|
15
|
+
# dispatch-release.sh (the bundled readers)
|
|
16
|
+
# - sdd-cli's catalogue build and repo-config reader
|
|
17
|
+
# - `sdd-cli migrate vocabulary` (rewrites an SDD's own files)
|
|
18
|
+
#
|
|
19
|
+
# Distribution: ships in every method baseline via the seed-file lists.
|
|
20
|
+
|
|
21
|
+
schema_version: "1.0.0"
|
|
22
|
+
introduced_in: "2.0.0" # the sdd-cli major that reads both names
|
|
23
|
+
removed_in: "3.0.0" # the sdd-cli major that rejects the former names
|
|
24
|
+
|
|
25
|
+
renames:
|
|
26
|
+
- id: authoring-status-detailed
|
|
27
|
+
old: detailed-pre-lock
|
|
28
|
+
new: detailed
|
|
29
|
+
positions:
|
|
30
|
+
- "spec `**Status:**` header (M-ADR 0201's authoring ladder)"
|
|
31
|
+
- "work-order status"
|
|
32
|
+
- "capability manifest `capability.status` (a spec-authoring stage on the wrong axis; reported, never translated)"
|
|
33
|
+
|
|
34
|
+
- id: stability-stable
|
|
35
|
+
old: locked
|
|
36
|
+
new: stable
|
|
37
|
+
positions:
|
|
38
|
+
- "`required_stability` (app and integration platform dependencies)"
|
|
39
|
+
- "`traceability.contracts.status` (the contract-set maturity ladder)"
|
|
40
|
+
- "repository config `stability` (a stability class)"
|
|
41
|
+
- "runtime-artefact `stability` and `template-stability`"
|
|
42
|
+
note: "Only where `locked` is a stability CLASS. A pinned version is a different thing (M-ADR 0222 §4.1)."
|
|
43
|
+
|
|
44
|
+
- id: agreement-tag
|
|
45
|
+
old: release-vMAJOR.MINOR-locked
|
|
46
|
+
new: release-vMAJOR.MINOR-agreed
|
|
47
|
+
positions:
|
|
48
|
+
- "the agreement tag on a release branch (M-ADRs 0072-0074)"
|
|
49
|
+
|
|
50
|
+
- id: agreement-tag-key
|
|
51
|
+
old: lock_in_tag
|
|
52
|
+
new: agreement_tag
|
|
53
|
+
positions:
|
|
54
|
+
- "a platform BASELINE.md record key (M-ADR 0073)"
|
|
55
|
+
|
|
56
|
+
- id: gate-registry-next-gate
|
|
57
|
+
old: lock-in
|
|
58
|
+
new: agreement
|
|
59
|
+
positions:
|
|
60
|
+
- "gate registry `next_gate` value (domain-architecture rubric)"
|
|
61
|
+
|
|
62
|
+
- id: gate-registry-remediation-key
|
|
63
|
+
old: remediation_before_lock_in
|
|
64
|
+
new: remediation_before_agreement
|
|
65
|
+
positions:
|
|
66
|
+
- "gate registry key (domain-architecture rubric)"
|
|
67
|
+
|
|
68
|
+
- id: journey-status-accepted
|
|
69
|
+
old: accepted-pre-lock
|
|
70
|
+
new: accepted
|
|
71
|
+
positions:
|
|
72
|
+
- "user-journey status (templates only; no reader)"
|
|
73
|
+
|
|
74
|
+
# An extension an adopter names itself (an `x-<adopter>-stability` contract extension,
|
|
75
|
+
# say) is the adopter's to rename; its values follow the method's (M-ADR 0222 §4.1). The migrate verb
|
|
76
|
+
# reports such extensions and never rewrites them.
|
package/dist/verify-version.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdd-method/maintain-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"sddCliVersion": "1.
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"sddCliVersion": "2.1.0",
|
|
5
5
|
"description": "Bring an SDD repository current and rebuild its derived model — sync, briefing, catalogue build and aggregate, pack build, context pull — plus every read-only verify verb. Ships only the code those verbs reach; it cannot author specification content. Authoring is in @sdd-method/sdd-cli.",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"type": "module",
|