@sdd-method/verify-cli 1.3.0 → 1.5.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/catalogue/canonical-schema.yaml +23 -23
- package/dist/lib/catalogue/coverage-predicates.js +4 -4
- package/dist/lib/catalogue/coverage-run.js +1 -1
- package/dist/lib/catalogue/finding-identity.js +3 -3
- package/dist/lib/catalogue/manifest-loader.js +2 -2
- package/dist/lib/catalogue/scaffold-delta.js +1 -1
- package/dist/lib/catalogue/schema-compat.js +1 -2
- package/dist/lib/catalogue/staleness-verdicts.js +2 -2
- package/dist/lib/lock-file.js +1 -1
- package/dist/lib/package-version.js +1 -1
- package/dist/lib/validate/behaviour-store.js +351 -0
- package/dist/lib/validate/dispatch.js +18 -0
- package/dist/lib/validate/index.js +1 -1
- package/dist/lib/validate/plan.js +3 -1
- package/dist/lib/validate/yaml-parse.js +1 -1
- package/dist/maintain-version.json +2 -2
- package/dist/verify-version.json +2 -2
- package/package.json +4 -6
- package/scripts/validate-app-dependencies.sh +5 -5
- package/scripts/validate-integration-docs.sh +1 -1
- package/LICENSE +0 -201
- package/NOTICE +0 -17
|
@@ -134,7 +134,7 @@ enums:
|
|
|
134
134
|
runtime: true
|
|
135
135
|
emits_service_row: false
|
|
136
136
|
description: |
|
|
137
|
-
Type of repository, declared in `orchestration/repo-config/*.yaml` as `service_type` and surfaced in `repositories.component_type` and `services.component_type`. The canonical builder uses this to decide which repos are runtime services (api_service, worker, gateway, frontend, mobile) and which are non-runtime (library, infrastructure, documentation); firmware runs, but on a device, so it is runtime without a service row (M-ADR 0215) — service emission follows emits_service_row, not runtime. Whether a repo is a vertical-domain application is conveyed by its SDD manifest's `profile: application-product`, not by a service_type tag — this enum is strictly about runtime artefact shape
|
|
137
|
+
Type of repository, declared in `orchestration/repo-config/*.yaml` as `service_type` and surfaced in `repositories.component_type` and `services.component_type`. The canonical builder uses this to decide which repos are runtime services (api_service, worker, gateway, frontend, mobile) and which are non-runtime (library, infrastructure, documentation); firmware runs, but on a device, so it is runtime without a service row (M-ADR 0215) — service emission follows emits_service_row, not runtime. Whether a repo is a vertical-domain application is conveyed by its SDD manifest's `profile: application-product`, not by a service_type tag — this enum is strictly about runtime artefact shape.
|
|
138
138
|
capability_exemption_reason:
|
|
139
139
|
values:
|
|
140
140
|
- value: infrastructure
|
|
@@ -174,11 +174,11 @@ enums:
|
|
|
174
174
|
- value: public-infrastructure
|
|
175
175
|
description: Public networks, registries, or services no party in the estate operates.
|
|
176
176
|
- value: estate-owned-unmodelled
|
|
177
|
-
description: "Operated by this estate but with no SDD or container home yet — the row is an accounted-for stand-in, and this population is the estate's modelling backlog, countable (
|
|
177
|
+
description: "Operated by this estate but with no SDD or container home yet — the row is an accounted-for stand-in, and this population is the estate's modelling backlog, countable (2.32.0). Not an assertion of externality: an assertion of accountability for a boundary row."
|
|
178
178
|
- value: organisation-internal
|
|
179
|
-
description: Owned elsewhere in the same organisation, outside this estate's modelling boundary (
|
|
179
|
+
description: Owned elsewhere in the same organisation, outside this estate's modelling boundary (2.32.0).
|
|
180
180
|
description: |
|
|
181
|
-
Why an external-system candidate is ACCOUNTED FOR at the estate boundary, per M-ADR 0189 §4.6 (
|
|
181
|
+
Why an external-system candidate is ACCOUNTED FOR at the estate boundary, per M-ADR 0189 §4.6 (wording corrected in 2.32.0): five values assert another party owns the system, and two assert the estate/organisation owns it but it has no model home yet — forcing the latter through the former would demand a false reason, and a false reason is worse than silence. The adjudicator can prove a candidate internal; it cannot prove one accounted-for — that is an assertion the author is accountable for, in the shape M-ADR 0188 ratified for repositories. Closed vocabulary, method-owned, extended only by ADR amendment, so adopters cannot locally widen the exemption surface to silence the unverified-candidate coverage finding.
|
|
182
182
|
ENFORCED, not advisory: the builder drops a row whose reason is outside this vocabulary (convention finding) and the candidate stays unverified — which is the point. An unrecognised reason must not succeed at the one thing an exemption does.
|
|
183
183
|
scaffold_status:
|
|
184
184
|
values:
|
|
@@ -1353,7 +1353,7 @@ catalogues:
|
|
|
1353
1353
|
nullable: true
|
|
1354
1354
|
description: The platform this row belongs to — sdd_id of its platform-root. Stamped by the aggregator per M-ADR 0208 §4.4 (preserve-if-set, fill rule by pull path); left empty by per-SDD builders. Added in 3.7.0.
|
|
1355
1355
|
description: |
|
|
1356
|
-
Hierarchical grouping for features. First-class entity
|
|
1356
|
+
Hierarchical grouping for features. First-class entity — replaces the previous reverse-only synthetic `feature-tree-group` rows in features.csv. Both forward and reverse can use feature_groups: forward application-product SDDs whose `feature-tree.md` has H2/H3 structure, reverse SDDs whose extracted feature trees are inherently hierarchical, and any SDD that promotes groups to first-class with authored metadata. Groups can nest via `parent_group_id`.
|
|
1357
1357
|
foreign_keys:
|
|
1358
1358
|
- column: product_id
|
|
1359
1359
|
references: products.id
|
|
@@ -2062,8 +2062,8 @@ catalogues:
|
|
|
2062
2062
|
nullable: false
|
|
2063
2063
|
description: Mandatory, non-empty. The reason makes exemptions countable; the note makes them reviewable (M-ADR 0188 §4.2 pattern).
|
|
2064
2064
|
description: |
|
|
2065
|
-
A declared, reasoned assertion that an external-system candidate is GENUINELY external (M-ADR 0189 §4.6
|
|
2066
|
-
The invariant it completes: c4_external_systems was the one catalogue family with no coverage predicate — a candidate producing no adjudication outcome was silent, and that silence hid a 28% dependency -graph distortion
|
|
2065
|
+
A declared, reasoned assertion that an external-system candidate is GENUINELY external (M-ADR 0189 §4.6). Annotation catalogue (M-ADR 0133 §4.2b): one FK, declared never derived, closed reason enum. Sourced from `orchestration/external-system-exemptions.yaml`.
|
|
2066
|
+
The invariant it completes: c4_external_systems was the one catalogue family with no coverage predicate — a candidate producing no adjudication outcome was silent, and that silence hid a 28% dependency -graph distortion. Every candidate now either resolves at adjudication, or carries an exemption here, or is reported unverified. An exemption retires only the unverified finding — never an adjudication outcome (a converted candidate's exemption is a contradiction, reported and dropped). Shared entities may be exempted by multiple SDDs: identical rows dedup at aggregation (same exception class as c4_external_systems); conflicting rows keep first-seen with a convention finding.
|
|
2067
2067
|
foreign_keys:
|
|
2068
2068
|
- column: external_system_id
|
|
2069
2069
|
references: c4_external_systems.id
|
|
@@ -2172,7 +2172,7 @@ catalogues:
|
|
|
2172
2172
|
type: string
|
|
2173
2173
|
nullable: false
|
|
2174
2174
|
description: exposes | consumes.
|
|
2175
|
-
description: "Edge: service exposes or consumes contract. Both FKs are external (per M-ADR 0147): federated SDDs that walk per-repo service-architecture tables emit consumes rows where service_id resolves to the platform-root catalogue's services.csv (post-aggregation) and contract_id resolves to the platform-root contracts.csv. Internal builds (
|
|
2175
|
+
description: "Edge: service exposes or consumes contract. Both FKs are external (per M-ADR 0147): federated SDDs that walk per-repo service-architecture tables emit consumes rows where service_id resolves to the platform-root catalogue's services.csv (post-aggregation) and contract_id resolves to the platform-root contracts.csv. Internal builds (a single-SDD platform) still optimistically validate locally. Schema 2.8.0."
|
|
2176
2176
|
foreign_keys:
|
|
2177
2177
|
- column: service_id
|
|
2178
2178
|
references: services.id
|
|
@@ -2303,7 +2303,7 @@ catalogues:
|
|
|
2303
2303
|
nullable: true
|
|
2304
2304
|
description: Where the coupling is evidenced — a path or anchor in the SDD, from `evidence:` on the services[] entry. Prose about the coupling stays in dependency_notes; this is the pointer a reviewer follows.
|
|
2305
2305
|
description: |
|
|
2306
|
-
Edge: capability is realised by services. Sourced from the capability traceability manifest services[] block. service_id is an external FK (schema 2.29.0
|
|
2306
|
+
Edge: capability is realised by services. Sourced from the capability traceability manifest services[] block. service_id is an external FK (schema 2.29.0): a traceability entry marked `owner_sdd:` declares a cross-SDD reference (e.g. the service moved to a sibling SDD at an M-ADR 0170 §4.2 carve) — the per-SDD builder emits the edge, logs it `[finding] external`, and aggregation validates it hard against the owning sibling's rows. An UNMARKED id that does not resolve locally is NOT emitted — it stays a member-tier orphan finding (or convention, when it names a known external system).
|
|
2307
2307
|
foreign_keys:
|
|
2308
2308
|
- column: capability_id
|
|
2309
2309
|
references: capabilities.id
|
|
@@ -2365,8 +2365,8 @@ catalogues:
|
|
|
2365
2365
|
- name: source
|
|
2366
2366
|
type: string
|
|
2367
2367
|
nullable: false
|
|
2368
|
-
description: "declared — the only emitted value. `derived` is reserved vocabulary: the reverse derivation it anticipated was never built (0 rows across 105 measured units) and the claim was corrected at 3.0.0 (M-ADR 0203 §4.5
|
|
2369
|
-
description: "Edge: capability is bound to a contract. Whether the capability exposes or depends on it is not stated by the source (traceability.contracts), so this description asserts no direction (M-ADR 0211 §1). Forward-declared only. Forward-declared only (traceability.contracts). The reverse derivation this description long claimed (service_contract ⋈ capability_service) was measured nonexistent — 0 `source: derived` rows across 105 units, builder references neither catalogue — and the CLAIM was corrected at 3.0.0 (M-ADR 0203 §4.5
|
|
2368
|
+
description: "declared — the only emitted value. `derived` is reserved vocabulary: the reverse derivation it anticipated was never built (0 rows across 105 measured units) and the claim was corrected at 3.0.0 (M-ADR 0203 §4.5); the value re-enters if a decision builds a reverse source."
|
|
2369
|
+
description: "Edge: capability is bound to a contract. Whether the capability exposes or depends on it is not stated by the source (traceability.contracts), so this description asserts no direction (M-ADR 0211 §1). Forward-declared only. Forward-declared only (traceability.contracts). The reverse derivation this description long claimed (service_contract ⋈ capability_service) was measured nonexistent — 0 `source: derived` rows across 105 units, builder references neither catalogue — and the CLAIM was corrected at 3.0.0 (M-ADR 0203 §4.5, gap class absent-derivation per M-ADR 0196 §4.1). The edge is forward-one-directional until a decision builds a reverse source."
|
|
2370
2370
|
foreign_keys:
|
|
2371
2371
|
- column: capability_id
|
|
2372
2372
|
references: capabilities.id
|
|
@@ -2546,7 +2546,7 @@ catalogues:
|
|
|
2546
2546
|
type: string
|
|
2547
2547
|
nullable: true
|
|
2548
2548
|
description: The platform this row belongs to — sdd_id of its platform-root. Stamped by the aggregator per M-ADR 0208 §4.4 (preserve-if-set, fill rule by pull path); left empty by per-SDD builders. Added in 3.7.0.
|
|
2549
|
-
description: "Edge: feature delivers async push via a named channel/topic. Peer of `graphql_operations:` in the per-feature evidence shape — `graphql_operations:` carries the GraphQL operation names declared in the schema (Queries / Mutations / Subscriptions); `feature_subscription_channels` rows carry the channel/topic layer that physically delivers the push events (AppSync WebSocket endpoints, Pekko WS channels, MQTT topics, server-sent-event streams).
|
|
2549
|
+
description: "Edge: feature delivers async push via a named channel/topic. Peer of `graphql_operations:` in the per-feature evidence shape — `graphql_operations:` carries the GraphQL operation names declared in the schema (Queries / Mutations / Subscriptions); `feature_subscription_channels` rows carry the channel/topic layer that physically delivers the push events (AppSync WebSocket endpoints, Pekko WS channels, MQTT topics, server-sent-event streams). A single GraphQL Subscription operation may listen on multiple channels; one channel may be consumed by multiple operations — modelling as a separate edge catalogue (not as a feature column) accommodates the many-to-many. Built per-SDD from `reverse-evidence/feature-grounding-evidence.yaml` `subscription_channels:` entries on each feature. Added in schema 2.9.0."
|
|
2550
2550
|
foreign_keys:
|
|
2551
2551
|
- column: feature_id
|
|
2552
2552
|
references: features.id
|
|
@@ -2672,9 +2672,9 @@ catalogues:
|
|
|
2672
2672
|
- name: interaction
|
|
2673
2673
|
type: string
|
|
2674
2674
|
nullable: true
|
|
2675
|
-
description: Optional interaction label from the Rel(...) line. May hold multiple '; '-joined labels
|
|
2675
|
+
description: Optional interaction label from the Rel(...) line. May hold multiple '; '-joined labels.
|
|
2676
2676
|
description: |
|
|
2677
|
-
Edge: a persona uses a C4 container — the L2-granularity companion to persona_c4system (M-ADR 0189
|
|
2677
|
+
Edge: a persona uses a C4 container — the L2-granularity companion to persona_c4system (M-ADR 0189, ratified 2026-07-10). Eleven of one estate's L2 diagrams and the bulk of another's 81 unroutable Rels drew Person→Container relationships that had no catalogue and raised no finding. Harvested at member tier from Rel(...) lines whose endpoints resolve to a file-local Person and a local container (either direction; the row is normalised persona→container). A Person that matches no persona row is a coverage finding, never a silent skip. Both authoring directions feed from the same diagram surface (M-ADR 0157 parity). Added in schema 2.34.0.
|
|
2678
2678
|
foreign_keys:
|
|
2679
2679
|
- column: persona_id
|
|
2680
2680
|
references: personas.id
|
|
@@ -2698,11 +2698,11 @@ catalogues:
|
|
|
2698
2698
|
- name: interaction
|
|
2699
2699
|
type: string
|
|
2700
2700
|
nullable: true
|
|
2701
|
-
description: Optional integration label/protocol from the Rel(...) line. May hold multiple '; '-joined labels when one pair is declared with distinct labels (
|
|
2701
|
+
description: Optional integration label/protocol from the Rel(...) line. May hold multiple '; '-joined labels when one pair is declared with distinct labels (2.33.2).
|
|
2702
2702
|
- name: from_container_id
|
|
2703
2703
|
type: string
|
|
2704
2704
|
nullable: true
|
|
2705
|
-
description: The Rel's non-external endpoint resolved to a local container at member tier, when it is one (
|
|
2705
|
+
description: The Rel's non-external endpoint resolved to a local container at member tier, when it is one (2.33.0) — the datum that lets adjudication rewrite this edge onto container_dependency instead of destroying it. Empty when the endpoint is the system itself or unresolvable.
|
|
2706
2706
|
description: "Edge: a C4 system integrates with an external software system. Captures the L1 context 'System integrates with External System' relationship, extracted from System_Ext(...) + Rel(...) on L1/L2 diagrams. Per M-ADR 0155. Container-level external dependencies are out of scope (system-level only). Added in schema 2.10.0. From 2.30.0 (M-ADR 0189 §4.4): edges whose external endpoint the aggregator adjudicates as an INTERNAL system are rewritten onto system_system_dependency and removed from this catalogue."
|
|
2707
2707
|
foreign_keys:
|
|
2708
2708
|
- column: c4_system_id
|
|
@@ -2734,13 +2734,13 @@ catalogues:
|
|
|
2734
2734
|
- name: source_sdd_id
|
|
2735
2735
|
type: string
|
|
2736
2736
|
nullable: true
|
|
2737
|
-
description: SDD whose diagram declared the originating reference. Member-emitted rows set it directly
|
|
2737
|
+
description: SDD whose diagram declared the originating reference. Member-emitted rows set it directly; adjudication-derived rows are stamped by the aggregator.
|
|
2738
2738
|
- name: platform_sdd_id
|
|
2739
2739
|
type: string
|
|
2740
2740
|
nullable: true
|
|
2741
2741
|
description: The platform this row belongs to — sdd_id of its platform-root. Stamped by the aggregator per M-ADR 0208 §4.4 (preserve-if-set, fill rule by pull path); left empty by per-SDD builders. Added in 3.7.0.
|
|
2742
2742
|
description: |
|
|
2743
|
-
Edge: a C4 container depends on another INTERNAL container (
|
|
2743
|
+
Edge: a C4 container depends on another INTERNAL container (the system-tier gap exactly one tier down: "no edge for internal" at L2). Derived by the aggregator at adjudication: an external-system candidate that resolves to an internal container was never external — each of its dependency edges whose member-resolved `from_container_id` is known is rewritten here, and a candidate with no remaining edges is dropped (it was a phantom duplicate of the container). A candidate with unconvertible edges is kept with the tier-contradiction finding, so no edge is ever destroyed to make a build quieter — the measured cost that stopped an adopter remediating 22 contradictions was eight real edges. ALSO emitted at member tier (2.33.1): the C4-correct authoring — Rel(container_a, container_b) between two Container(...) nodes — emits its edge directly; adjudication merges into a catalogue that already has content rather than being its only source, so the metamodel no longer rewards mis-declaration over correct authoring. Added in schema 2.33.0.
|
|
2744
2744
|
foreign_keys:
|
|
2745
2745
|
- column: from_container_id
|
|
2746
2746
|
references: c4_containers.id
|
|
@@ -2910,7 +2910,7 @@ catalogues:
|
|
|
2910
2910
|
- name: target_locus
|
|
2911
2911
|
type: string
|
|
2912
2912
|
nullable: true
|
|
2913
|
-
description: platform | external | legacy (M-ADR 0204; closed enum, absent/empty = platform). A statement about the TARGET only — never about the consuming product, and never a stability, lifecycle or quality claim. platform rows behave exactly as before this column existed. external/legacy rows carry empty target_capability_id and target_service_id and name their target in target_external_system_id; they skip platform resolution and the
|
|
2913
|
+
description: platform | external | legacy (M-ADR 0204; closed enum, absent/empty = platform). A statement about the TARGET only — never about the consuming product, and never a stability, lifecycle or quality claim. platform rows behave exactly as before this column existed. external/legacy rows carry empty target_capability_id and target_service_id and name their target in target_external_system_id; they skip platform resolution and the synchronous realiser-join, with every skip named.
|
|
2914
2914
|
- name: target_external_system_id
|
|
2915
2915
|
type: string
|
|
2916
2916
|
nullable: true
|
|
@@ -2926,7 +2926,7 @@ catalogues:
|
|
|
2926
2926
|
- name: contract_type
|
|
2927
2927
|
type: string
|
|
2928
2928
|
nullable: true
|
|
2929
|
-
description: rest | graphql | openapi | grpc | event | event-stream | stream | other. Optional; reflects what the consuming SDD declares. The async set (`event`/`event-stream`/`stream`) marks a shared-stream subscription that binds to a stream contract rather than a realiser service (M-ADR 0192) — such rows skip the
|
|
2929
|
+
description: rest | graphql | openapi | grpc | event | event-stream | stream | other. Optional; reflects what the consuming SDD declares. The async set (`event`/`event-stream`/`stream`) marks a shared-stream subscription that binds to a stream contract rather than a realiser service (M-ADR 0192) — such rows skip the synchronous realiser-join at aggregation.
|
|
2930
2930
|
- name: required_stability
|
|
2931
2931
|
type: string
|
|
2932
2932
|
nullable: true
|
|
@@ -2948,7 +2948,7 @@ catalogues:
|
|
|
2948
2948
|
nullable: true
|
|
2949
2949
|
description: The platform this row belongs to — sdd_id of its platform-root. Stamped by the aggregator per M-ADR 0208 §4.4 (preserve-if-set, fill rule by pull path); left empty by per-SDD builders. Added in 3.7.0.
|
|
2950
2950
|
description: |
|
|
2951
|
-
Per-SDD edge: an application-side or integration-side SDD declares that its product depends on a platform-side capability + service pair. Built from `reverse-evidence/feature-grounding-evidence.yaml` `platform_dependencies:` block (product-level — the cohort source shape carries product-scoped dependency rows with a `consumed_by:` list naming which services within the consuming product depend on the target). Each row records one (consuming product, target capability, target service) triple. The aggregator then joins these rows against platform-side `capability_service` to emit `cross_sdd_platform_capability` edges at aggregation time. Cross- SDD references (target_capability_id, target_service_id) are external — they're not expected to resolve in the per-SDD build, only at aggregation. Added in schema 2.9.0
|
|
2951
|
+
Per-SDD edge: an application-side or integration-side SDD declares that its product depends on a platform-side capability + service pair. Built from `reverse-evidence/feature-grounding-evidence.yaml` `platform_dependencies:` block (product-level — the cohort source shape carries product-scoped dependency rows with a `consumed_by:` list naming which services within the consuming product depend on the target). Each row records one (consuming product, target capability, target service) triple. The aggregator then joins these rows against platform-side `capability_service` to emit `cross_sdd_platform_capability` edges at aggregation time. Cross- SDD references (target_capability_id, target_service_id) are external — they're not expected to resolve in the per-SDD build, only at aggregation. Added in schema 2.9.0.
|
|
2952
2952
|
foreign_keys:
|
|
2953
2953
|
- column: product_id
|
|
2954
2954
|
references: products.id
|
|
@@ -2993,7 +2993,7 @@ catalogues:
|
|
|
2993
2993
|
nullable: false
|
|
2994
2994
|
description: Service identifier the consuming SDD references and the platform SDD owns. The join key.
|
|
2995
2995
|
description: |
|
|
2996
|
-
Federated edge: an application-side or integration-side SDD's `product_platform_dependencies` reference resolves against a platform-side SDD's capability spec via the platform capability's `capability_service` roster. Surfaces ecosystem-wide platform- consumer relationships at product → capability granularity. Built **only at aggregation time** — `sdd-cli catalogue aggregate` joins each consuming SDD's `product_platform_dependencies` rows against every other SDD's `capability_service` rows on `target_service_id == service_id`, emitting one row per resolved (consuming-product, platform-capability) pair. Per-SDD builders emit nothing for this catalogue; the aggregator owns it entirely. A platform-dependency whose target_service_id doesn't resolve to any platform-side capability_service row is logged as an `orphan` finding and produces no row. Cross-SDD references are external by definition. Added in schema 2.9.0; see
|
|
2996
|
+
Federated edge: an application-side or integration-side SDD's `product_platform_dependencies` reference resolves against a platform-side SDD's capability spec via the platform capability's `capability_service` roster. Surfaces ecosystem-wide platform- consumer relationships at product → capability granularity. Built **only at aggregation time** — `sdd-cli catalogue aggregate` joins each consuming SDD's `product_platform_dependencies` rows against every other SDD's `capability_service` rows on `target_service_id == service_id`, emitting one row per resolved (consuming-product, platform-capability) pair. Per-SDD builders emit nothing for this catalogue; the aggregator owns it entirely. A platform-dependency whose target_service_id doesn't resolve to any platform-side capability_service row is logged as an `orphan` finding and produces no row. Cross-SDD references are external by definition. Added in schema 2.9.0; see M-ADR 0134.
|
|
2997
2997
|
foreign_keys:
|
|
2998
2998
|
- column: consuming_product_id
|
|
2999
2999
|
references: products.id
|
|
@@ -3131,7 +3131,7 @@ catalogues:
|
|
|
3131
3131
|
nullable: true
|
|
3132
3132
|
description: The platform this row belongs to — sdd_id of its platform-root. Stamped by the aggregator per M-ADR 0208 §4.4 (preserve-if-set, fill rule by pull path); left empty by per-SDD builders. Added in 3.7.0.
|
|
3133
3133
|
description: |
|
|
3134
|
-
Application-product annotation (M-ADR 0191 §4.3 overlay, second element
|
|
3134
|
+
Application-product annotation (M-ADR 0191 §4.3 overlay, second element): one row per conformance tag the product asserts about its archetype's conformance expectations. Built per-SDD from the manifest's `conformance` block so the AGGREGATION-tier conformance GATE can read it. `status=covered` means the product's own conformance suite exercises the tag; `status=deviated` means the product declares (with a rationale) that it deliberately does not. At aggregation, an archetype-expected conformance tag that is neither covered nor deviated raises a deviation-visible WARN. The method gates the DECLARATION — it does not run the adopter's conformance suite. Added in schema 2.39.0.
|
|
3135
3135
|
foreign_keys:
|
|
3136
3136
|
- column: product_id
|
|
3137
3137
|
references: products.id
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
* §4.3 (`orphaned` is aggregate-only; member tier reports
|
|
14
14
|
* `unresolved-locally`, cause-agnostic); the Path-X leverage axis is
|
|
15
15
|
* reported separately and only when repo_capability.csv exists.
|
|
16
|
-
* - event_coverage
|
|
16
|
+
* - event_coverage: every catalogued event is attributed to a
|
|
17
17
|
* runtime — service_event edge or resolving publisher column — with
|
|
18
18
|
* the contract-twin alternative route, the column-vs-edge disagreement
|
|
19
19
|
* controls, and the chain-readout/consumer-enumerability metric lines
|
|
20
20
|
* that are never verdicts.
|
|
21
|
-
* - contract_coverage
|
|
21
|
+
* - contract_coverage: every catalogued contract is reachable —
|
|
22
22
|
* capability_contract or service_contract — with the three computed
|
|
23
23
|
* exclusions (deprecated / provider-surface / not-an-interface, the
|
|
24
24
|
* last a positive READ of the spec file), the covered-as-event
|
|
@@ -178,8 +178,8 @@ FROM "services" s
|
|
|
178
178
|
WHERE s."id" IS NOT NULL AND s."id" <> ''
|
|
179
179
|
ORDER BY service_id, primary_repo;`;
|
|
180
180
|
export async function runServiceCapabilityCoverage(session, options) {
|
|
181
|
-
// Known-bad probe, carried over from the reference implementation
|
|
182
|
-
//
|
|
181
|
+
// Known-bad probe, carried over from the reference implementation:
|
|
182
|
+
// a synthetic claimed service must classify
|
|
183
183
|
// as claimed BEFORE any real data is reported — a run whose
|
|
184
184
|
// classification never executed must not be able to report a healthy
|
|
185
185
|
// estate.
|
|
@@ -89,7 +89,7 @@ export async function runCatalogueCoverage(inputs, write) {
|
|
|
89
89
|
const available = new Set(schema.catalogues.map((c) => c.name).filter((n) => present(n)));
|
|
90
90
|
lines.push(...renderScoresCoverage(await runScoresCoverage(session, { tier, available, sddRoot }), tier));
|
|
91
91
|
// Staleness VERDICTS — the second half of the fourteenth check's §4.5
|
|
92
|
-
// scope
|
|
92
|
+
// scope, member-tier only with the aggregate skip stated.
|
|
93
93
|
// Same coverage class, same reporting-only staging as the rest of the
|
|
94
94
|
// scores family.
|
|
95
95
|
lines.push(...renderStalenessVerdicts(await runStalenessVerdicts(session, { tier, available, sddRoot }), tier));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Finding IDENTITY — the stable key a findings baseline is compared on, per
|
|
3
|
-
* M-ADR 0132 §4.3 as amended
|
|
3
|
+
* M-ADR 0132 §4.3 as amended.
|
|
4
4
|
*
|
|
5
5
|
* THE PROBLEM THIS EXISTS FOR, measured rather than asserted. A findings
|
|
6
6
|
* baseline stores whole `[finding]` lines, so improving a message invalidates
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* files, 74 non-empty, 1,677 body lines — that works for 1,434 lines (85.5%)
|
|
18
18
|
* and fails for 243 (14.5%), which embed their subject in prose:
|
|
19
19
|
*
|
|
20
|
-
* [finding] orphan: repo integration-
|
|
20
|
+
* [finding] orphan: repo integration-acme resolves to no capability ...
|
|
21
21
|
*
|
|
22
22
|
* One renderer template, all orphan class, spread across 62 repos. Recovering
|
|
23
23
|
* identity there means teaching the comparator the renderer's message
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* baseline is reviewed by people and a side-channel nobody sees is a side
|
|
31
31
|
* channel nobody maintains:
|
|
32
32
|
*
|
|
33
|
-
* [finding] orphan (repo:integration-
|
|
33
|
+
* [finding] orphan (repo:integration-acme): repo integration-acme ...
|
|
34
34
|
* [finding] divergence (docs/domains/asset/product/x.traceability.yaml): ...
|
|
35
35
|
*
|
|
36
36
|
* A finding with no declared subject renders EXACTLY as it does today. That is
|
|
@@ -123,7 +123,7 @@ const DataPlaneSchema = z.object({
|
|
|
123
123
|
bindings: z.array(DataPlaneBindingSchema),
|
|
124
124
|
deviations: z.array(DataPlaneDeviationSchema).optional(),
|
|
125
125
|
});
|
|
126
|
-
// Conformance coverage (M-ADR 0191 §4.3, second overlay element
|
|
126
|
+
// Conformance coverage (M-ADR 0191 §4.3, second overlay element). An
|
|
127
127
|
// application-product asserts which of its archetype's conformance expectations
|
|
128
128
|
// its own conformance suite exercises (`coverage`), and declares a rationale for
|
|
129
129
|
// any expected tag it deliberately does not cover (`deviations`). The method
|
|
@@ -249,7 +249,7 @@ const ManifestSchema = z.object({
|
|
|
249
249
|
// optional declared deviations from the archetype default, each carrying a
|
|
250
250
|
// rationale so the mismatch is sanctioned variance, not silent drift.
|
|
251
251
|
data_plane: DataPlaneSchema.optional(),
|
|
252
|
-
// Conformance coverage declaration (M-ADR 0191 §4.3
|
|
252
|
+
// Conformance coverage declaration (M-ADR 0191 §4.3, sdd-manifest-schema
|
|
253
253
|
// 1.5.0). Which archetype conformance expectations the product's suite
|
|
254
254
|
// exercises, plus rationales for any it deliberately does not.
|
|
255
255
|
conformance: ConformanceSchema.optional(),
|
|
@@ -216,7 +216,7 @@ export async function runScaffoldDelta(session, options) {
|
|
|
216
216
|
}
|
|
217
217
|
/**
|
|
218
218
|
* The delta's DEFECT classes are gate-acting (`divergence`), PROMOTED by a
|
|
219
|
-
* recorded
|
|
219
|
+
* recorded method act after the M-ADR 0207 §4.9 pilot completed with no
|
|
220
220
|
* surprise in any gated figure (2026-08-08) — the deliberate later act the
|
|
221
221
|
* scores-coverage precedent reserves, performed. `scaffold-stale` stays
|
|
222
222
|
* `coverage` with its staleness family: a stale anchor is a re-declare
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Per-schema compatibility ranges declared by this sdd-cli build.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* sdd-method-distributed schemas version independently. The CLI declares
|
|
4
|
+
* The three sdd-method-distributed schemas version independently. The CLI declares
|
|
6
5
|
* the version range it accepts for each and fails fast at read time on
|
|
7
6
|
* out-of-range manifests.
|
|
8
7
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// Staleness VERDICTS — the second half of M-ADR 0205 §4.5's staleness scope
|
|
2
|
-
//
|
|
1
|
+
// Staleness VERDICTS — the second half of M-ADR 0205 §4.5's staleness scope:
|
|
2
|
+
// "staleness meaning corpus_ref older than the target's last
|
|
3
3
|
// content change". The scores-coverage predicate computes each row's BASIS;
|
|
4
4
|
// this module computes the VERDICT the basis supports. A stale score is worse
|
|
5
5
|
// than a missing one, because it reads as coverage — the predicate otherwise
|
package/dist/lib/lock-file.js
CHANGED
|
@@ -215,7 +215,7 @@ function parseVersioned(raw) {
|
|
|
215
215
|
// a YAML reader parses 113 of 325 entries in one real lock as MAPPINGS
|
|
216
216
|
// rather than strings — silently, with no error. Quoting on write is the
|
|
217
217
|
// fix; reading both forms ships first so no lock can be written that an
|
|
218
|
-
// older reader cannot parse.
|
|
218
|
+
// older reader cannot parse.
|
|
219
219
|
const entry = stripQuotes(line.slice(" - ".length));
|
|
220
220
|
const withDigest = DIGEST_ENTRY_RE.exec(entry);
|
|
221
221
|
if (withDigest) {
|
|
@@ -83,7 +83,7 @@ export function getVerifyCliVersion() {
|
|
|
83
83
|
}
|
|
84
84
|
/**
|
|
85
85
|
* The @sdd-method/maintain-cli version released WITH this sdd-cli. The managed CI workflow's
|
|
86
|
-
* catalogue freshness step installs exactly this version (M-ADR 0218 §5
|
|
86
|
+
* catalogue freshness step installs exactly this version (M-ADR 0218 §5).
|
|
87
87
|
* Read the same way as the verify version, for the same reason (dist/ copy, never scripts/ live).
|
|
88
88
|
*/
|
|
89
89
|
export function getMaintainCliVersion() {
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
// Behaviour-store checks (M-ADR 0221, 0222, 0223): the gates OpenSpec itself does not enforce.
|
|
2
|
+
//
|
|
3
|
+
// An SDD's behaviour store is an OpenSpec project; in the inside-SDD shape it is `openspec/` at the
|
|
4
|
+
// SDD root, with one agreement record per change at `docs/agreements/<change>.yaml`
|
|
5
|
+
// (agreement-record-schema.yaml). OpenSpec checks the format of specs and deltas. It does not check
|
|
6
|
+
// that a change was agreed before it was built, that the requirements it modifies stayed as they
|
|
7
|
+
// were agreed, or that an archived change was judged. These are those checks:
|
|
8
|
+
//
|
|
9
|
+
// 1. records: every agreement record has the shape the checks below read.
|
|
10
|
+
// 2. agreement: an open change with any completed task has an agreed record and a Gate 0 that
|
|
11
|
+
// ends CLEAR (the pre-apply check, M-ADR 0223 §4.3).
|
|
12
|
+
// 3. stale: for each open, agreed change that MODIFIES, REMOVES or RENAMES requirements, the
|
|
13
|
+
// targeted living specs are unchanged since the agreed commit (M-ADR 0223 §4.5).
|
|
14
|
+
// With no such targets the check is vacuous, and says so.
|
|
15
|
+
// 4. verdicts: every archived change except a baseline has a verdict.md and a record whose
|
|
16
|
+
// verdict is PASS and names it (M-ADR 0223 §4.6).
|
|
17
|
+
//
|
|
18
|
+
// A sibling-repository store (M-ADR 0221 §4.2) is checked in that repository, not here.
|
|
19
|
+
//
|
|
20
|
+
// OPENSPEC'S OWN VALIDATION is the separate validator `behaviour-store-openspec`, because it needs
|
|
21
|
+
// the OpenSpec CLI at OPENSPEC_VERSION (the Method Validation module installs it). Without a store
|
|
22
|
+
// both report [N/A], an empty population, never a pass.
|
|
23
|
+
import { execFile } from "node:child_process";
|
|
24
|
+
import { access, readdir, readFile } from "node:fs/promises";
|
|
25
|
+
import { basename, join } from "node:path";
|
|
26
|
+
import { promisify } from "node:util";
|
|
27
|
+
import { parseDocument } from "yaml";
|
|
28
|
+
const execFileAsync = promisify(execFile);
|
|
29
|
+
/** The OpenSpec release the method depends on (M-ADR 0223 ruling 2). The module installs exactly this. */
|
|
30
|
+
export const OPENSPEC_VERSION = "1.14.3";
|
|
31
|
+
/** Telemetry off for every OpenSpec run the method makes. */
|
|
32
|
+
export const OPENSPEC_ENV = { OPENSPEC_TELEMETRY: "0", DO_NOT_TRACK: "1" };
|
|
33
|
+
export const STORE_DIR = "openspec";
|
|
34
|
+
export const AGREEMENTS_DIR = "docs/agreements";
|
|
35
|
+
const AGREEMENT_STATUSES = ["proposed", "agreed", "superseded", "withdrawn"];
|
|
36
|
+
const COMMIT_RE = /^[0-9a-f]{7,40}$/;
|
|
37
|
+
// OpenSpec counts a task done when its box holds only `x`, either case, any spacing.
|
|
38
|
+
const TASK_DONE_RE = /^\s*-\s*\[\s*[xX]\s*\]/m;
|
|
39
|
+
const TARGET_HEADING_RE = /^##\s+(MODIFIED|REMOVED|RENAMED)\s+Requirements\b/m;
|
|
40
|
+
async function exists(p) {
|
|
41
|
+
try {
|
|
42
|
+
await access(p);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function dirs(p) {
|
|
50
|
+
try {
|
|
51
|
+
return (await readdir(p, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function readText(p) {
|
|
58
|
+
try {
|
|
59
|
+
return await readFile(p, "utf8");
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Read and shape-check one agreement record. Returns the record, or the problems found. */
|
|
66
|
+
export function parseAgreementRecord(file, text) {
|
|
67
|
+
const problems = [];
|
|
68
|
+
const doc = parseDocument(text);
|
|
69
|
+
if (doc.errors.length > 0) {
|
|
70
|
+
return { problems: doc.errors.map((e) => `${file}: does not parse: ${e.message.split("\n")[0]}`) };
|
|
71
|
+
}
|
|
72
|
+
const root = doc.toJS();
|
|
73
|
+
const r = root?.agreement_record;
|
|
74
|
+
if (!r || typeof r !== "object")
|
|
75
|
+
return { problems: [`${file}: no top-level agreement_record`] };
|
|
76
|
+
const expected = basename(file).replace(/\.ya?ml$/, "");
|
|
77
|
+
if (r.change !== expected)
|
|
78
|
+
problems.push(`${file}: change is ${JSON.stringify(r.change)}, expected "${expected}" (the file name)`);
|
|
79
|
+
const status = typeof r.status === "string" ? r.status : "";
|
|
80
|
+
if (!AGREEMENT_STATUSES.includes(status)) {
|
|
81
|
+
problems.push(`${file}: status ${JSON.stringify(r.status)} is not one of ${AGREEMENT_STATUSES.join(" | ")}`);
|
|
82
|
+
}
|
|
83
|
+
const sdd = (r.sdd ?? {});
|
|
84
|
+
const gates = Array.isArray(r.gates) ? r.gates : [];
|
|
85
|
+
const gate0 = gates.find((g) => g && g.name === "gate-0");
|
|
86
|
+
if (status === "agreed") {
|
|
87
|
+
if (r.agreed_on === undefined || r.agreed_on === null)
|
|
88
|
+
problems.push(`${file}: agreed, but no agreed_on`);
|
|
89
|
+
if (typeof r.approved_by !== "string" || r.approved_by.trim() === "")
|
|
90
|
+
problems.push(`${file}: agreed, but no approved_by`);
|
|
91
|
+
if (typeof sdd.commit !== "string") {
|
|
92
|
+
problems.push(`${file}: sdd.commit must be a quoted string, found ${sdd.commit === undefined ? "nothing" : `${typeof sdd.commit} ${String(sdd.commit)}`}` +
|
|
93
|
+
(typeof sdd.commit === "number" ? " (unquoted, YAML parsed the SHA as a number)" : ""));
|
|
94
|
+
}
|
|
95
|
+
else if (!COMMIT_RE.test(sdd.commit)) {
|
|
96
|
+
problems.push(`${file}: sdd.commit "${sdd.commit}" is not 7-40 hex characters`);
|
|
97
|
+
}
|
|
98
|
+
if (!gate0)
|
|
99
|
+
problems.push(`${file}: agreed, but no gates entry named gate-0`);
|
|
100
|
+
else if (gate0.result !== "CLEAR")
|
|
101
|
+
problems.push(`${file}: agreed, but gate-0 result is ${JSON.stringify(gate0.result)}, not CLEAR`);
|
|
102
|
+
}
|
|
103
|
+
// Every commit a record pins must survive parsing as a string. YAML 1.2 reads an unquoted
|
|
104
|
+
// all-digit abbreviation as an integer and one shaped like `1234e567` or `123e456` as a float
|
|
105
|
+
// (Infinity), and the pin is then lost; checking the PARSED type is the only test that catches
|
|
106
|
+
// both, since a pattern applied after coercion sees "Infinity" or a number.
|
|
107
|
+
for (const [path, value] of commitFields(r)) {
|
|
108
|
+
if (path === "sdd.commit")
|
|
109
|
+
continue;
|
|
110
|
+
if (typeof value !== "string") {
|
|
111
|
+
problems.push(`${file}: ${path} must be a quoted string, found ${typeof value} ${String(value)} (unquoted, YAML parsed the SHA as a number)`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const verdict = r.verdict && typeof r.verdict === "object" ? r.verdict : undefined;
|
|
115
|
+
if (problems.length > 0)
|
|
116
|
+
return { problems };
|
|
117
|
+
return {
|
|
118
|
+
record: {
|
|
119
|
+
file,
|
|
120
|
+
change: expected,
|
|
121
|
+
status,
|
|
122
|
+
...(typeof sdd.commit === "string" ? { commit: sdd.commit } : {}),
|
|
123
|
+
...(typeof gate0?.result === "string" ? { gate0: gate0.result } : {}),
|
|
124
|
+
...(verdict ? { verdict } : {}),
|
|
125
|
+
},
|
|
126
|
+
problems,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/** Every pinned commit in a record: any key named `commit` or ending `_commit`, and each value under verdict.merged and current. */
|
|
130
|
+
function commitFields(r) {
|
|
131
|
+
const out = [];
|
|
132
|
+
const walk = (v, path, pinMap) => {
|
|
133
|
+
if (Array.isArray(v))
|
|
134
|
+
v.forEach((x, i) => walk(x, `${path}[${i}]`, false));
|
|
135
|
+
else if (v && typeof v === "object") {
|
|
136
|
+
for (const [k, x] of Object.entries(v)) {
|
|
137
|
+
const p = path === "" ? k : `${path}.${k}`;
|
|
138
|
+
const isPin = pinMap || k === "commit" || k.endsWith("_commit");
|
|
139
|
+
if (isPin && (x === null || typeof x !== "object"))
|
|
140
|
+
out.push([p, x]);
|
|
141
|
+
else
|
|
142
|
+
walk(x, p, p === "verdict.merged" || p === "current");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
walk(r, "", false);
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
function lastGateLine(text) {
|
|
150
|
+
const lines = text.split("\n").map((l) => l.trim()).filter((l) => l !== "");
|
|
151
|
+
return lines.at(-1) ?? "";
|
|
152
|
+
}
|
|
153
|
+
async function gitDiffQuiet(repo, commit, path) {
|
|
154
|
+
try {
|
|
155
|
+
await execFileAsync("git", ["-C", repo, "cat-file", "-e", `${commit}^{commit}`]);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return "unresolved";
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
await execFileAsync("git", ["-C", repo, "diff", "--quiet", commit, "HEAD", "--", path]);
|
|
162
|
+
return "same";
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
return err.code === 1 ? "changed" : "unresolved";
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/** The living specs an open change's deltas modify, remove or rename: openspec/specs/<capability>/spec.md. */
|
|
169
|
+
async function targetedSpecs(repo, changeDir) {
|
|
170
|
+
const out = [];
|
|
171
|
+
const walk = async (rel) => {
|
|
172
|
+
const abs = join(repo, changeDir, "specs", rel);
|
|
173
|
+
let entries;
|
|
174
|
+
try {
|
|
175
|
+
entries = await readdir(abs, { withFileTypes: true });
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
for (const e of entries) {
|
|
181
|
+
const childRel = rel === "" ? e.name : `${rel}/${e.name}`;
|
|
182
|
+
if (e.isDirectory())
|
|
183
|
+
await walk(childRel);
|
|
184
|
+
else if (e.name === "spec.md") {
|
|
185
|
+
const text = (await readText(join(abs, e.name))) ?? "";
|
|
186
|
+
if (TARGET_HEADING_RE.test(text))
|
|
187
|
+
out.push(`${STORE_DIR}/specs/${rel}/spec.md`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
await walk("");
|
|
192
|
+
return out.sort();
|
|
193
|
+
}
|
|
194
|
+
export async function checkBehaviourStore(repo) {
|
|
195
|
+
if (!(await exists(join(repo, STORE_DIR)))) {
|
|
196
|
+
return { exitCode: 0, stdout: `[N/A] no behaviour store (${STORE_DIR}/) in this repository\n` };
|
|
197
|
+
}
|
|
198
|
+
const lines = [];
|
|
199
|
+
const failures = [];
|
|
200
|
+
// 1. records
|
|
201
|
+
const records = new Map();
|
|
202
|
+
let recordFiles = [];
|
|
203
|
+
try {
|
|
204
|
+
recordFiles = (await readdir(join(repo, AGREEMENTS_DIR))).filter((f) => /\.ya?ml$/.test(f)).sort();
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
recordFiles = [];
|
|
208
|
+
}
|
|
209
|
+
for (const f of recordFiles) {
|
|
210
|
+
const rel = `${AGREEMENTS_DIR}/${f}`;
|
|
211
|
+
const { record, problems } = parseAgreementRecord(rel, (await readText(join(repo, rel))) ?? "");
|
|
212
|
+
failures.push(...problems);
|
|
213
|
+
if (record)
|
|
214
|
+
records.set(record.change, record);
|
|
215
|
+
}
|
|
216
|
+
lines.push(`records: ${recordFiles.length} agreement record(s), ${recordFiles.length - records.size} malformed`);
|
|
217
|
+
// 2 and 3. open changes
|
|
218
|
+
const open = (await dirs(join(repo, STORE_DIR, "changes"))).filter((d) => d !== "archive");
|
|
219
|
+
let started = 0;
|
|
220
|
+
let staleChecked = 0;
|
|
221
|
+
for (const change of open) {
|
|
222
|
+
const dir = `${STORE_DIR}/changes/${change}`;
|
|
223
|
+
const tasks = await readText(join(repo, dir, "tasks.md"));
|
|
224
|
+
const record = records.get(change);
|
|
225
|
+
if (tasks !== null && TASK_DONE_RE.test(tasks)) {
|
|
226
|
+
started++;
|
|
227
|
+
if (!record)
|
|
228
|
+
failures.push(`${dir}: has completed tasks but no agreement record (${AGREEMENTS_DIR}/${change}.yaml)`);
|
|
229
|
+
else if (record.status !== "agreed")
|
|
230
|
+
failures.push(`${dir}: has completed tasks but its agreement record is ${record.status}, not agreed`);
|
|
231
|
+
const gate = await readText(join(repo, dir, "gate-0.md"));
|
|
232
|
+
if (gate === null)
|
|
233
|
+
failures.push(`${dir}: has completed tasks but no gate-0.md`);
|
|
234
|
+
else if (lastGateLine(gate) !== "GATE: CLEAR")
|
|
235
|
+
failures.push(`${dir}: has completed tasks but gate-0.md ends "${lastGateLine(gate)}", not "GATE: CLEAR"`);
|
|
236
|
+
}
|
|
237
|
+
if (record?.status === "agreed" && record.commit) {
|
|
238
|
+
for (const spec of await targetedSpecs(repo, dir)) {
|
|
239
|
+
staleChecked++;
|
|
240
|
+
const r = await gitDiffQuiet(repo, record.commit, spec);
|
|
241
|
+
if (r === "changed")
|
|
242
|
+
failures.push(`${dir}: STALE: ${spec} changed since the agreed commit ${record.commit}`);
|
|
243
|
+
if (r === "unresolved")
|
|
244
|
+
failures.push(`${dir}: stale check unresolved: commit ${record.commit} is not in this clone's history (a shallow clone cannot run it)`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
lines.push(`agreement: ${open.length} open change(s), ${started} with completed tasks`);
|
|
249
|
+
lines.push(staleChecked === 0 ? "stale: vacuous (no open agreed change modifies, removes or renames a requirement)" : `stale: ${staleChecked} targeted living spec(s) checked against their agreed commits`);
|
|
250
|
+
// 4. archived changes
|
|
251
|
+
const archived = await dirs(join(repo, STORE_DIR, "changes", "archive"));
|
|
252
|
+
let baselines = 0;
|
|
253
|
+
for (const name of archived) {
|
|
254
|
+
const dir = `${STORE_DIR}/changes/archive/${name}`;
|
|
255
|
+
if (await exists(join(repo, dir, "baseline-evidence.md"))) {
|
|
256
|
+
baselines++;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const change = name.replace(/^\d{4}-\d{2}-\d{2}-/, "");
|
|
260
|
+
if (!(await exists(join(repo, dir, "verdict.md"))))
|
|
261
|
+
failures.push(`${dir}: archived without verdict.md`);
|
|
262
|
+
const record = records.get(change);
|
|
263
|
+
if (!record) {
|
|
264
|
+
failures.push(`${dir}: archived without an agreement record (${AGREEMENTS_DIR}/${change}.yaml)`);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (record.verdict?.result !== "PASS")
|
|
268
|
+
failures.push(`${record.file}: change is archived but verdict.result is ${JSON.stringify(record.verdict?.result)}, not PASS`);
|
|
269
|
+
const vr = record.verdict?.record;
|
|
270
|
+
if (typeof vr !== "string" || !(await exists(join(repo, vr))))
|
|
271
|
+
failures.push(`${record.file}: verdict.record ${JSON.stringify(vr)} does not name an existing file`);
|
|
272
|
+
}
|
|
273
|
+
lines.push(`verdicts: ${archived.length} archived change(s), ${baselines} baseline(s), ${archived.length - baselines} needing a PASS verdict`);
|
|
274
|
+
const population = recordFiles.length + open.length + archived.length;
|
|
275
|
+
const body = lines.map((l) => ` ${l}`).join("\n") + "\n";
|
|
276
|
+
if (failures.length > 0) {
|
|
277
|
+
return { exitCode: 1, stdout: body + failures.map((f) => `[FAIL] ${f}`).join("\n") + "\n" };
|
|
278
|
+
}
|
|
279
|
+
if (population === 0)
|
|
280
|
+
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 verdict checks hold\n" };
|
|
282
|
+
}
|
|
283
|
+
async function runOpenspec(repo, args) {
|
|
284
|
+
try {
|
|
285
|
+
const { stdout } = await execFileAsync("openspec", args, { cwd: repo, env: { ...process.env, ...OPENSPEC_ENV }, maxBuffer: 16 * 1024 * 1024 });
|
|
286
|
+
return { code: 0, json: JSON.parse(stdout), raw: stdout };
|
|
287
|
+
}
|
|
288
|
+
catch (err) {
|
|
289
|
+
const e = err;
|
|
290
|
+
let json;
|
|
291
|
+
try {
|
|
292
|
+
json = JSON.parse(e.stdout ?? "");
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
json = undefined;
|
|
296
|
+
}
|
|
297
|
+
return { code: typeof e.code === "number" ? e.code : 127, ...(json ? { json } : {}), raw: (e.stdout ?? "") + (e.stderr ?? "") };
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/** OpenSpec's own strict validation of the store: specs and open changes, and archived changes' tasks. */
|
|
301
|
+
export async function checkBehaviourStoreOpenspec(repo) {
|
|
302
|
+
if (!(await exists(join(repo, STORE_DIR)))) {
|
|
303
|
+
return { exitCode: 0, stdout: `[N/A] no behaviour store (${STORE_DIR}/) in this repository\n` };
|
|
304
|
+
}
|
|
305
|
+
let version = "";
|
|
306
|
+
try {
|
|
307
|
+
version = (await execFileAsync("openspec", ["--version"], { env: { ...process.env, ...OPENSPEC_ENV } })).stdout.trim();
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
return {
|
|
311
|
+
exitCode: 1,
|
|
312
|
+
stdout: `[FAIL] the OpenSpec CLI is not on PATH; this repository has a behaviour store, so its validation cannot run (install @fission-ai/openspec@${OPENSPEC_VERSION}; the Method Validation module does)\n`,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
if (version !== OPENSPEC_VERSION) {
|
|
316
|
+
return { exitCode: 1, stdout: `[FAIL] OpenSpec ${version} is installed; the method depends on exactly ${OPENSPEC_VERSION}\n` };
|
|
317
|
+
}
|
|
318
|
+
const lines = [];
|
|
319
|
+
const failures = [];
|
|
320
|
+
let measured = 0;
|
|
321
|
+
for (const [label, args] of [
|
|
322
|
+
["specs and open changes (--all --strict)", ["validate", "--all", "--strict", "--json", "--no-interactive"]],
|
|
323
|
+
["archived changes' tasks (--archived)", ["validate", "--archived", "--json", "--no-interactive"]],
|
|
324
|
+
]) {
|
|
325
|
+
const r = await runOpenspec(repo, [...args]);
|
|
326
|
+
const t = r.json?.summary?.totals;
|
|
327
|
+
if (!t) {
|
|
328
|
+
failures.push(`${label}: OpenSpec returned no report (exit ${r.code}): ${r.raw.trim().split("\n").slice(-3).join(" | ")}`);
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
measured += t.items ?? 0;
|
|
332
|
+
lines.push(`${label}: ${t.items ?? 0} item(s), ${t.passed ?? 0} passed, ${t.failed ?? 0} failed`);
|
|
333
|
+
for (const item of r.json?.items ?? []) {
|
|
334
|
+
if (item.valid)
|
|
335
|
+
continue;
|
|
336
|
+
for (const i of item.issues ?? [])
|
|
337
|
+
failures.push(`${label}: ${item.id}${i.path ? ` (${i.path})` : ""}: ${i.level} ${i.message}`);
|
|
338
|
+
if ((item.issues ?? []).length === 0)
|
|
339
|
+
failures.push(`${label}: ${item.id}: invalid`);
|
|
340
|
+
}
|
|
341
|
+
if (r.code !== 0 && (t.failed ?? 0) === 0)
|
|
342
|
+
failures.push(`${label}: OpenSpec exited ${r.code} with no failed item reported`);
|
|
343
|
+
}
|
|
344
|
+
const body = [` OpenSpec ${version}`, ...lines.map((l) => ` ${l}`)].join("\n") + "\n";
|
|
345
|
+
if (failures.length > 0)
|
|
346
|
+
return { exitCode: 1, stdout: body + failures.map((f) => `[FAIL] ${f}`).join("\n") + "\n" };
|
|
347
|
+
if (measured === 0)
|
|
348
|
+
return { exitCode: 0, stdout: body + "[N/A] OpenSpec found no specs and no changes to validate\n" };
|
|
349
|
+
return { exitCode: 0, stdout: body + "[PASS] OpenSpec strict validation holds\n" };
|
|
350
|
+
}
|
|
351
|
+
//# sourceMappingURL=behaviour-store.js.map
|
|
@@ -1,5 +1,20 @@
|
|
|
1
|
+
import { checkBehaviourStore, checkBehaviourStoreOpenspec } from "./behaviour-store.js";
|
|
1
2
|
import { checkBundleCoherence } from "./bundle-coherence.js";
|
|
2
3
|
import { checkYamlParse } from "./yaml-parse.js";
|
|
4
|
+
// The behaviour store (M-ADR 0221-0223) can sit in an SDD of any profile, so every profile carries
|
|
5
|
+
// its two checks; without a store both report [N/A], an empty population, not a pass.
|
|
6
|
+
const BEHAVIOUR_STORE_VALIDATORS = [
|
|
7
|
+
{
|
|
8
|
+
name: "behaviour-store",
|
|
9
|
+
source: { kind: "native", run: async (ctx) => checkBehaviourStore(ctx.targetRepo) },
|
|
10
|
+
build: () => ({ status: "run", args: [] }),
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: "behaviour-store-openspec",
|
|
14
|
+
source: { kind: "native", run: async (ctx) => checkBehaviourStoreOpenspec(ctx.targetRepo) },
|
|
15
|
+
build: () => ({ status: "run", args: [] }),
|
|
16
|
+
},
|
|
17
|
+
];
|
|
3
18
|
// Validators that accept --app-root / --repo-root flags work fine from the
|
|
4
19
|
// bundled location because they override their internal ROOT_DIR via the
|
|
5
20
|
// flag. Validators without that flag (adr-structure, runway-classification,
|
|
@@ -74,6 +89,7 @@ const APP_VALIDATORS = [
|
|
|
74
89
|
},
|
|
75
90
|
build: () => ({ status: "run", args: [] }),
|
|
76
91
|
},
|
|
92
|
+
...BEHAVIOUR_STORE_VALIDATORS,
|
|
77
93
|
// Note: validate-runway-classification is intentionally NOT in the app set.
|
|
78
94
|
// It requires platform-level files (docs/platform/feature-tree.md,
|
|
79
95
|
// architecture-runway-index.md) that app repos don't own. Per ADR 0089,
|
|
@@ -126,6 +142,7 @@ const INTEGRATION_VALIDATORS = [
|
|
|
126
142
|
},
|
|
127
143
|
build: () => ({ status: "run", args: [] }),
|
|
128
144
|
},
|
|
145
|
+
...BEHAVIOUR_STORE_VALIDATORS,
|
|
129
146
|
];
|
|
130
147
|
const PLATFORM_VALIDATORS = [
|
|
131
148
|
{
|
|
@@ -208,6 +225,7 @@ const PLATFORM_VALIDATORS = [
|
|
|
208
225
|
},
|
|
209
226
|
build: () => ({ status: "run", args: [] }),
|
|
210
227
|
},
|
|
228
|
+
...BEHAVIOUR_STORE_VALIDATORS,
|
|
211
229
|
// Optional: the upstream validate-docs.sh dispatcher covers the 6
|
|
212
230
|
// conditional validators (backward-compatibility, deprecation-runway,
|
|
213
231
|
// migration-completeness, ddd-artifacts, glossary-alignment,
|
|
@@ -54,7 +54,7 @@ export async function runValidate(opts, stdout = (line) => process.stdout.write(
|
|
|
54
54
|
// passed" with none were previously the same line (M-ADR 0200 §4.3). The
|
|
55
55
|
// exit code is deliberately unchanged — the defect is the claim, not the
|
|
56
56
|
// gating; whether an unexecutable validator should FAIL is the open design
|
|
57
|
-
// question
|
|
57
|
+
// question, not this one.
|
|
58
58
|
function verdict(summary, selected) {
|
|
59
59
|
if (summary.exitCode !== 0)
|
|
60
60
|
return fail("One or more validators failed");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The method-managed validation job, as DATA (
|
|
2
|
+
* The method-managed validation job, as DATA (M-ADR 0219).
|
|
3
3
|
*
|
|
4
4
|
* One ordered plan per profile: every default validator, the platform's script-only checks, then the
|
|
5
5
|
* advisory catalogue freshness check. Every executor renders or runs THIS: the GitHub workflow
|
|
@@ -22,6 +22,8 @@ const TITLES = {
|
|
|
22
22
|
"yaml-parse": "Validate that tracked YAML parses",
|
|
23
23
|
"gate-readiness": "Validate gate readiness",
|
|
24
24
|
"bundle-coherence": "Report bundle coherence",
|
|
25
|
+
"behaviour-store": "Validate the behaviour store: agreement records, the pre-apply gate, stale requirements, verdicts",
|
|
26
|
+
"behaviour-store-openspec": "Validate the behaviour store with OpenSpec (strict)",
|
|
25
27
|
// CI has no platform checkout, so this step runs the app-only checks; the met gate needs the platform beside it.
|
|
26
28
|
"app-dependencies": "Validate app dependencies (app-only checks; no platform checkout in CI)",
|
|
27
29
|
};
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// with it certifies exactly the corruption a line-surgery transformer
|
|
11
11
|
// produces. `yaml` v2 refuses duplicate keys (DUPLICATE_KEY) and the other
|
|
12
12
|
// malformations a lenient parse glosses — e.g. a `: ` inside an unquoted
|
|
13
|
-
// scalar (`path: (none —
|
|
13
|
+
// scalar (`path: (none — see: docs)`), which no pattern rule catches.
|
|
14
14
|
//
|
|
15
15
|
// Scope: the YAML files git TRACKS (`*.yaml`, `*.yml`) — the repo's sources,
|
|
16
16
|
// not build output or dependencies. Outside a git work tree there is no
|
package/dist/verify-version.json
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdd-method/verify-cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"sddCliVersion": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"sddCliVersion": "1.46.0",
|
|
5
5
|
"description": "Verify an SDD repository without changing it — validate, list, and read-only catalogue and pack checks. Ships only the code those verbs reach; it cannot write to the repository it is pointed at. Authoring and maintenance are in @sdd-method/sdd-cli.",
|
|
6
|
-
"license": "
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"bin": {
|
|
9
9
|
"sdd-verify": "dist/verify-bin.js"
|
|
@@ -23,8 +23,6 @@
|
|
|
23
23
|
"files": [
|
|
24
24
|
"dist",
|
|
25
25
|
"scripts",
|
|
26
|
-
"README.md"
|
|
27
|
-
"LICENSE",
|
|
28
|
-
"NOTICE"
|
|
26
|
+
"README.md"
|
|
29
27
|
]
|
|
30
28
|
}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
# 3b. (platform) that contract_path resolves to a file under the platform root
|
|
21
21
|
#
|
|
22
22
|
# Outputs a dependency status table.
|
|
23
|
-
# Used as the Stage 6
|
|
23
|
+
# Used as the Stage 6 agreement gate in the App Exploration Workflow.
|
|
24
24
|
#
|
|
25
25
|
# THE STABILITY VOCABULARY, AND WHY IT IS WHAT IT IS
|
|
26
26
|
# `required_stability` states the CONSUMING app's expectation of contract
|
|
@@ -157,8 +157,8 @@ APP_DEPS_FILE="$APP_ROOT/app-platform-dependencies.yaml"
|
|
|
157
157
|
#
|
|
158
158
|
# This gate read only the forward file, so an entire equal-status source went
|
|
159
159
|
# ungated. Measured in one reverse estate: 15 evidence files declaring 65
|
|
160
|
-
# further dependencies, 38 of them `locked` — precisely the
|
|
161
|
-
# a Stage 6
|
|
160
|
+
# further dependencies, 38 of them `locked` — precisely the population
|
|
161
|
+
# a Stage 6 agreement gate exists to check — none of which this gate could see.
|
|
162
162
|
#
|
|
163
163
|
# A source that the catalogue reads and the gate does not is a coverage gap by
|
|
164
164
|
# construction, so the gate now reads both.
|
|
@@ -637,7 +637,7 @@ main() {
|
|
|
637
637
|
fi
|
|
638
638
|
|
|
639
639
|
if [[ "$FAILURES" -gt 0 ]]; then
|
|
640
|
-
log_fail "$FAILURES dependency check(s) failed. Stage 6
|
|
640
|
+
log_fail "$FAILURES dependency check(s) failed. Stage 6 agreement gate NOT met."
|
|
641
641
|
exit 1
|
|
642
642
|
fi
|
|
643
643
|
|
|
@@ -656,7 +656,7 @@ main() {
|
|
|
656
656
|
if [[ "$WARNINGS" -gt 0 ]]; then
|
|
657
657
|
log_warn "All required checks passed with $WARNINGS warning(s)."
|
|
658
658
|
else
|
|
659
|
-
log_pass "All dependency checks passed. Stage 6
|
|
659
|
+
log_pass "All dependency checks passed. Stage 6 agreement gate met."
|
|
660
660
|
fi
|
|
661
661
|
exit 0
|
|
662
662
|
}
|
|
@@ -341,7 +341,7 @@ check_grounding_doc() {
|
|
|
341
341
|
|
|
342
342
|
local grounding="$REPO_ROOT/docs/design/integration/feature-grounding.md"
|
|
343
343
|
if [[ ! -f "$grounding" ]]; then
|
|
344
|
-
log_warn "docs/design/integration/feature-grounding.md not found — create before
|
|
344
|
+
log_warn "docs/design/integration/feature-grounding.md not found — create before agreement"
|
|
345
345
|
return
|
|
346
346
|
fi
|
|
347
347
|
|
package/LICENSE
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
Apache License
|
|
2
|
-
Version 2.0, January 2004
|
|
3
|
-
http://www.apache.org/licenses/
|
|
4
|
-
|
|
5
|
-
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
-
|
|
7
|
-
1. Definitions.
|
|
8
|
-
|
|
9
|
-
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
-
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
-
|
|
12
|
-
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
-
the copyright owner that is granting the License.
|
|
14
|
-
|
|
15
|
-
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
-
other entities that control, are controlled by, or are under common
|
|
17
|
-
control with that entity. For the purposes of this definition,
|
|
18
|
-
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
-
direction or management of such entity, whether by contract or
|
|
20
|
-
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
-
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
-
|
|
23
|
-
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
-
exercising permissions granted by this License.
|
|
25
|
-
|
|
26
|
-
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
-
including but not limited to software source code, documentation
|
|
28
|
-
source, and configuration files.
|
|
29
|
-
|
|
30
|
-
"Object" form shall mean any form resulting from mechanical
|
|
31
|
-
transformation or translation of a Source form, including but
|
|
32
|
-
not limited to compiled object code, generated documentation,
|
|
33
|
-
and conversions to other media types.
|
|
34
|
-
|
|
35
|
-
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
-
Object form, made available under the License, as indicated by a
|
|
37
|
-
copyright notice that is included in or attached to the work
|
|
38
|
-
(an example is provided in the Appendix below).
|
|
39
|
-
|
|
40
|
-
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
-
form, that is based on (or derived from) the Work and for which the
|
|
42
|
-
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
-
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
-
of this License, Derivative Works shall not include works that remain
|
|
45
|
-
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
-
the Work and Derivative Works thereof.
|
|
47
|
-
|
|
48
|
-
"Contribution" shall mean any work of authorship, including
|
|
49
|
-
the original version of the Work and any modifications or additions
|
|
50
|
-
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
-
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
-
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
-
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
-
means any form of electronic, verbal, or written communication sent
|
|
55
|
-
to the Licensor or its representatives, including but not limited to
|
|
56
|
-
communication on electronic mailing lists, source code control systems,
|
|
57
|
-
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
-
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
-
excluding communication that is conspicuously marked or otherwise
|
|
60
|
-
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
-
|
|
62
|
-
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
-
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
-
subsequently incorporated within the Work.
|
|
65
|
-
|
|
66
|
-
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
-
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
-
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
-
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
-
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
-
Work and such Derivative Works in Source or Object form.
|
|
72
|
-
|
|
73
|
-
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
-
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
-
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
-
(except as stated in this section) patent license to make, have made,
|
|
77
|
-
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
-
where such license applies only to those patent claims licensable
|
|
79
|
-
by such Contributor that are necessarily infringed by their
|
|
80
|
-
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
-
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
-
institute patent litigation against any entity (including a
|
|
83
|
-
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
-
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
-
or contributory patent infringement, then any patent licenses
|
|
86
|
-
granted to You under this License for that Work shall terminate
|
|
87
|
-
as of the date such litigation is filed.
|
|
88
|
-
|
|
89
|
-
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
-
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
-
modifications, and in Source or Object form, provided that You
|
|
92
|
-
meet the following conditions:
|
|
93
|
-
|
|
94
|
-
(a) You must give any other recipients of the Work or
|
|
95
|
-
Derivative Works a copy of this License; and
|
|
96
|
-
|
|
97
|
-
(b) You must cause any modified files to carry prominent notices
|
|
98
|
-
stating that You changed the files; and
|
|
99
|
-
|
|
100
|
-
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
-
that You distribute, all copyright, patent, trademark, and
|
|
102
|
-
attribution notices from the Source form of the Work,
|
|
103
|
-
excluding those notices that do not pertain to any part of
|
|
104
|
-
the Derivative Works; and
|
|
105
|
-
|
|
106
|
-
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
-
distribution, then any Derivative Works that You distribute must
|
|
108
|
-
include a readable copy of the attribution notices contained
|
|
109
|
-
within such NOTICE file, excluding those notices that do not
|
|
110
|
-
pertain to any part of the Derivative Works, in at least one
|
|
111
|
-
of the following places: within a NOTICE text file distributed
|
|
112
|
-
as part of the Derivative Works; within the Source form or
|
|
113
|
-
documentation, if provided along with the Derivative Works; or,
|
|
114
|
-
within a display generated by the Derivative Works, if and
|
|
115
|
-
wherever such third-party notices normally appear. The contents
|
|
116
|
-
of the NOTICE file are for informational purposes only and
|
|
117
|
-
do not modify the License. You may add Your own attribution
|
|
118
|
-
notices within Derivative Works that You distribute, alongside
|
|
119
|
-
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
-
that such additional attribution notices cannot be construed
|
|
121
|
-
as modifying the License.
|
|
122
|
-
|
|
123
|
-
You may add Your own copyright statement to Your modifications and
|
|
124
|
-
may provide additional or different license terms and conditions
|
|
125
|
-
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
-
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
-
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
-
the conditions stated in this License.
|
|
129
|
-
|
|
130
|
-
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
-
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
-
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
-
this License, without any additional terms or conditions.
|
|
134
|
-
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
-
the terms of any separate license agreement you may have executed
|
|
136
|
-
with Licensor regarding such Contributions.
|
|
137
|
-
|
|
138
|
-
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
-
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
-
except as required for reasonable and customary use in describing the
|
|
141
|
-
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
-
|
|
143
|
-
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
-
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
-
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
-
implied, including, without limitation, any warranties or conditions
|
|
148
|
-
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
-
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
-
appropriateness of using or redistributing the Work and assume any
|
|
151
|
-
risks associated with Your exercise of permissions under this License.
|
|
152
|
-
|
|
153
|
-
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
-
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
-
unless required by applicable law (such as deliberate and grossly
|
|
156
|
-
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
-
liable to You for damages, including any direct, indirect, special,
|
|
158
|
-
incidental, or consequential damages of any character arising as a
|
|
159
|
-
result of this License or out of the use or inability to use the
|
|
160
|
-
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
-
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
-
other commercial damages or losses), even if such Contributor
|
|
163
|
-
has been advised of the possibility of such damages.
|
|
164
|
-
|
|
165
|
-
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
-
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
-
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
-
or other liability obligations and/or rights consistent with this
|
|
169
|
-
License. However, in accepting such obligations, You may act only
|
|
170
|
-
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
-
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
-
defend, and hold each Contributor harmless for any liability
|
|
173
|
-
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
-
of your accepting any such warranty or additional liability.
|
|
175
|
-
|
|
176
|
-
END OF TERMS AND CONDITIONS
|
|
177
|
-
|
|
178
|
-
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
-
|
|
180
|
-
To apply the Apache License to your work, attach the following
|
|
181
|
-
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
-
replaced with your own identifying information. (Don't include
|
|
183
|
-
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
-
comment syntax for the file format. We also recommend that a
|
|
185
|
-
file or class name and description of purpose be included on the
|
|
186
|
-
same "printed page" as the copyright notice for easier
|
|
187
|
-
identification within third-party archives.
|
|
188
|
-
|
|
189
|
-
Copyright [yyyy] [name of copyright owner]
|
|
190
|
-
|
|
191
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
-
you may not use this file except in compliance with the License.
|
|
193
|
-
You may obtain a copy of the License at
|
|
194
|
-
|
|
195
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
-
|
|
197
|
-
Unless required by applicable law or agreed to in writing, software
|
|
198
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
-
See the License for the specific language governing permissions and
|
|
201
|
-
limitations under the License.
|
package/NOTICE
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
sdd-cli
|
|
2
|
-
Copyright 2026 Tom Foster
|
|
3
|
-
|
|
4
|
-
This product includes software developed as part of the SDD method
|
|
5
|
-
distribution.
|
|
6
|
-
|
|
7
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
-
you may not use this file except in compliance with the License.
|
|
9
|
-
You may obtain a copy of the License at
|
|
10
|
-
|
|
11
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
-
|
|
13
|
-
Unless required by applicable law or agreed to in writing, software
|
|
14
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
-
See the License for the specific language governing permissions and
|
|
17
|
-
limitations under the License.
|