xdrs-core 0.39.0 → 0.40.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.
@@ -22,6 +22,7 @@ Foundational standards, principles, and guidelines.
22
22
  - [_core-adr-policy-014](principles/014-standard-scope-type.md) - **standard scope type** — Defines the `standard` scope type: the default for business areas, products, and teams
23
23
  - [_core-adr-policy-015](principles/015-local-scope-type.md) - **_local scope type** — Defines the `_local` scope type: reserved for workspace-local decisions that must never be distributed
24
24
  - [_core-adr-policy-016](principles/016-policy-subjects.md) - **Policy Subjects** — Allowed subjects per type (ADR/BDR/EDR), descriptions, examples, and disambiguation tiebreakers
25
+ - [_core-adr-policy-017](principles/017-policy-numbering-ranges.md) - **Policy numbering ranges** — Subject-based 100-number block ranges for policy numbering and the freeze-reference exemption
25
26
 
26
27
  ## Skills
27
28
 
@@ -93,6 +93,7 @@ ADR and EDR share the same six technical subject names (see [`_core-adr-policy-0
93
93
  - `follows` (optional): Core scope names whose Policies apply as mandatory conventions to this scope, beyond `_core`. Last-listed takes precedence on conflicts (e.g., `follows: myarea-core, shared-standards`).
94
94
  - `related-scopes` (optional): Scope names of parent, sibling, or child scopes. Use when structural links help verify policy correctness across related scopes.
95
95
  - **Subjects:** The subject folder MUST be chosen per [`_core-adr-policy-016`](016-policy-subjects.md), which defines allowed subjects, full descriptions, examples, and disambiguation tiebreakers.
96
+ - **Policy numbering:** Numbers MUST be assigned within the subject-based block ranges defined in [`_core-adr-policy-017`](017-policy-numbering-ranges.md). Use the lowest available number within the block for the chosen subject. See [`_core-adr-policy-002-policy-standards.01-freeze-reference-exemption`](002-policy-standards.md) for exemptions.
96
97
  - MUST NOT use emojis
97
98
  - **Links:** Use relative paths for all links; MUST NOT use absolute paths starting with `/`.
98
99
  - **Indexes**
@@ -31,6 +31,7 @@ Policy documents are the authoritative source of truth for their scope, type, an
31
31
  | `valid-from` | Yes | ISO date (`YYYY-MM-DD`) indicating from when this decision MUST be enforced. Before this date it SHOULD be used everywhere possible, but compliance is not enforced during reviews until after this date. Defaults to the date the Policy was created. When updating an existing Policy whose `valid-from` date has already passed, preserve the original date—it MUST NOT be updated to the current date. The historical date shows when the policy was originally enabled and is important for understanding policy evolution. |
32
32
  | `license` | No | SPDX license expression (e.g. `MIT`, `Apache-2.0`, `CC-BY-4.0`). Indicates the license under which the document content is shared. If omitted, the license is governed by the repository or package defaults. |
33
33
  | `metadata` | No | Arbitrary key-value map for additional properties not defined by this spec. |
34
+ | `freeze-reference` | No | Boolean (`true` or `false`, defaults to `false`). When `true`, the policy MUST NOT be renamed, renumbered, or moved because external parties reference it by its current identity (path, number, name, heading). Reference-identity issues are intentionally exempt. See rule [`01-freeze-reference-exemption`](#01-freeze-reference-exemption). |
34
35
 
35
36
  - Minimal example:
36
37
  ```yaml
@@ -60,7 +61,8 @@ Policy documents are the authoritative source of truth for their scope, type, an
60
61
  - Research documents MAY be added under the same subject to capture the exploration, findings, and proposals that backed a decision. Research is useful during elaboration, discussion, and updates of Policies, but the Policy document remains the source of truth.
61
62
  - **Policy Id:** [scope]-[type]-policy-[number] (numbers are scoped per type+scope combination and MUST NOT be reused within that combination; MUST be lowercase)
62
63
  - Types in IDs: `adr-policy`, `bdr-policy`, `edr-policy`
63
- - Define the next number of a Policy by checking what is the highest number present in the type+scope. Don't fill numbering gaps, as they might be old deleted Policies and we MUST NOT reuse numbers of different documents/decisions. Numbering gaps are expected.
64
+ - Policy numbers MUST follow the subject-based block ranges defined in [`_core-adr-policy-017`](017-policy-numbering-ranges.md). Each subject has a reserved 100-number block; use the lowest available number within the block for the chosen subject. Use the overflow range (901–999) only when the subject's block is exhausted.
65
+ - Numbering gaps are expected and MUST NOT be filled, as gaps may represent deleted policies whose numbers MUST NOT be reused.
64
66
  - Policies MUST be concise and reference other Policies to avoid duplication.
65
67
  - Policies MUST NOT contain duplicated content: the same rule or information MUST NOT appear more than once in the document, whether stated identically or rephrased. Consolidate into a single authoritative statement and reference it where needed.
66
68
  - Policies MUST NOT contain conflicting content: all rules and statements within the same document MUST be consistent with each other.
@@ -86,6 +88,17 @@ Policy documents are the authoritative source of truth for their scope, type, an
86
88
  - Exceptions can reach under 2600 words (templates, more elaborate decision implementations etc)
87
89
  - MUST use `_local` scope if the user doesn't explicitly indicate a specific scope while creating a policy or skill.
88
90
 
91
+ #### 01-freeze-reference-exemption
92
+
93
+ When a policy's frontmatter includes `freeze-reference: true`, the policy MUST NOT be renamed, renumbered, or moved because external parties reference it by its current identity. The policy's reference identity is its full addressable path and canonical identifier: scope location, type folder, subject folder, number, filename, frontmatter `name` field, and document heading identifier. All checks and reviews that would require changing the reference identity MUST be ignored unless the owner explicitly requests them to be fixed. Specifically, the following checks are exempt:
94
+
95
+ - Invalid subject folder (scope, subject, or type placement does not match allowed values)
96
+ - Numbering range mismatch (number outside the reserved block for the subject per [`_core-adr-policy-017`](017-policy-numbering-ranges.md))
97
+ - Document heading format mismatch (heading does not start with the expected `# scope-type-policy-number:` prefix)
98
+ - Frontmatter `name` field mismatch (name does not match the expected identifier derived from the file path)
99
+
100
+ Content-level checks that do not affect the policy's reference (required sections, word count, emojis, broken outgoing links, structured rule block format, normative language) remain enforced. The `valid-from` date is not taken into consideration when evaluating whether `freeze-reference` applies.
101
+
89
102
  **Policy template**
90
103
 
91
104
  All Policies MUST follow this template
@@ -23,10 +23,10 @@ Each type uses a small, well-separated set of subjects. ADR and EDR share identi
23
23
 
24
24
  #### 01-subject-must-be-from-allowed-list
25
25
 
26
- The subject folder for any XDRS document MUST be one of the subjects allowed for its type, as listed below. A document placed in any other subject name is invalid.
26
+ The subject folder MUST be one of the subjects listed below. `freeze-reference: true` exempts this check (see [`_core-adr-policy-002-policy-standards.01-freeze-reference-exemption`](002-policy-standards.md)).
27
27
 
28
28
  | Subject | ADR | BDR | EDR |
29
- |---|---|---|---|
29
+ |---|---|---|
30
30
  | `principles` | ✓ | ✓ | ✓ |
31
31
  | `application` | ✓ | | ✓ |
32
32
  | `data` | ✓ | | ✓ |
@@ -42,7 +42,7 @@ A document MUST only be placed in `principles` when its content affects potentia
42
42
 
43
43
  #### 03-adr-and-edr-share-subject-names-at-different-abstraction-levels
44
44
 
45
- ADR and EDR intentionally share the same six subject names. The type disambiguates the abstraction level: ADR captures architectural choices about what to adopt or how systems relate at a high level; EDR captures engineering decisions about how to implement those choices in detail. When both an architectural and an engineering decision exist on the same topic, authors MUST create two separate documents. See tiebreaker 16 for the decision test.
45
+ ADR and EDR intentionally share the same six subject names. The type disambiguates the abstraction level: ADR captures architectural choices about what to adopt or how systems relate at a high level; EDR captures engineering decisions about how to implement those choices in detail. When both an architectural and an engineering decision exist on the same topic, authors MUST create two separate documents. See tiebreaker 18 for the decision test.
46
46
 
47
47
  #### 04-subject-descriptions-for-adr
48
48
 
@@ -84,7 +84,7 @@ When a document could plausibly fit more than one subject, authors MUST apply th
84
84
 
85
85
  2. `application` vs `governance` (EDR): `application` covers how code is written — patterns, conventions, and in-code practices. `governance` covers mandatory controls enforced externally as a gate or automated scan. Security headers written into HTTP responses MUST go in `application`; a mandatory security scanning gate in CI MUST go in `governance`. Rate limiting middleware in code MUST go in `application`; a gateway-enforced rate limit threshold gate MUST go in `governance`. Container security hardening in Dockerfiles MUST go in `platform`; a mandatory container image vulnerability scan gate in CI MUST go in `governance`.
86
86
 
87
- 3. `platform` vs `governance` (ADR): `platform` covers capabilities you adopt as part of the runtime. `governance` covers security or compliance controls you enforce. SSO/IAM infrastructure MUST go in `platform`; encryption baseline policy and data classification tiers MUST go in `governance`. See tiebreaker 17 for extended security infrastructure guidance.
87
+ 3. `platform` vs `governance` (ADR): `platform` covers capabilities you adopt as part of the runtime. `governance` covers security or compliance controls you enforce. SSO/IAM infrastructure MUST go in `platform`; encryption baseline policy and data classification tiers MUST go in `governance`. See tiebreaker 19 for extended security infrastructure guidance.
88
88
 
89
89
  4. `application` vs `data` (EDR): `application` covers the implementation pattern in code. `data` covers storage, schema, and data management. A caching strategy MUST go in `application`; ORM configuration and database migration tooling MUST go in `data`.
90
90
 
@@ -98,29 +98,29 @@ When a document could plausibly fit more than one subject, authors MUST apply th
98
98
 
99
99
  9. Change management: Process automation and delivery pipeline steps MUST go in `platform`. Human authorization and approval policies for changes MUST go in `governance`.
100
100
 
101
- 10. `platform` abstraction level across ADR and EDR: ADR/platform covers the architectural choice — what to adopt, which topology, which strategy. EDR/platform covers the implementation conventions — tool configuration, pipeline stages, naming standards, and developer environment setup. A FinOps cost allocation strategy MUST go in ADR/platform; tagging naming conventions MUST go in EDR/platform. When a concern spans both layers, create two separate documents. See tiebreaker 16 for the general ADR-vs-EDR test.
101
+ 10. `platform` abstraction level across ADR and EDR: ADR/platform covers the architectural choice — what to adopt, which topology, which strategy. EDR/platform covers the implementation conventions — tool configuration, pipeline stages, naming standards, and developer environment setup. A FinOps cost allocation strategy MUST go in ADR/platform; tagging naming conventions MUST go in EDR/platform. When a concern spans both layers, create two separate documents. See tiebreaker 18 for the general ADR-vs-EDR test.
102
102
 
103
103
  11. On-call rotation across BDR and ADR: The on-call ownership model and system accountability structure MUST go in ADR `operations`; on-call scheduling, rotation cadence, and compensation policy MUST go in BDR `operations`.
104
104
 
105
- 10. AI/ML decisions: Model selection and inference architecture MUST go in ADR `application`; AI/ML inference implementation patterns MUST go in EDR `application`. Training pipeline infrastructure (GPUs, orchestration, experiment tracking) MUST go in `platform`. Dataset management, feature stores, model registries, and data labeling standards MUST go in `data`.
105
+ 12. AI/ML decisions: Model selection and inference architecture MUST go in ADR `application`; AI/ML inference implementation patterns MUST go in EDR `application`. Training pipeline infrastructure (GPUs, orchestration, experiment tracking) MUST go in `platform`. Dataset management, feature stores, model registries, and data labeling standards MUST go in `data`.
106
106
 
107
- 11. AI ethics, responsible AI, and agentic governance: Business-level AI ethics and responsible AI policy MUST go in BDR `governance`. Mandatory architecture-level AI controls (AI risk assessment, model risk governance) MUST go in ADR `governance`. Automated AI compliance gates in CI/CD (bias scanning, model validation) MUST go in EDR `governance`. In-code guardrails, prompt safety patterns, and agentic workflow implementation MUST go in EDR `application`. Human-in-the-loop thresholds as runtime behavior MUST go in ADR `operations`.
107
+ 13. AI ethics, responsible AI, and agentic governance: Business-level AI ethics and responsible AI policy MUST go in BDR `governance`. Mandatory architecture-level AI controls (AI risk assessment, model risk governance) MUST go in ADR `governance`. Automated AI compliance gates in CI/CD (bias scanning, model validation) MUST go in EDR `governance`. In-code guardrails, prompt safety patterns, and agentic workflow implementation MUST go in EDR `application`. Human-in-the-loop thresholds as runtime behavior MUST go in ADR `operations`.
108
108
 
109
- 12. External technical vendor placement: Adopting a managed external technical service as a runtime capability (LLM API provider, observability SaaS, data warehouse) MUST go in ADR `platform`. Integrating with a third-party API as part of an application flow (payment gateway, identity provider, partner API) MUST go in ADR `application`. Risk assessment and due diligence for any external vendor MUST go in BDR `governance`. The business process for selecting and onboarding vendors MUST go in BDR `operations`.
109
+ 14. External technical vendor placement: Adopting a managed external technical service as a runtime capability (LLM API provider, observability SaaS, data warehouse) MUST go in ADR `platform`. Integrating with a third-party API as part of an application flow (payment gateway, identity provider, partner API) MUST go in ADR `application`. Risk assessment and due diligence for any external vendor MUST go in BDR `governance`. The business process for selecting and onboarding vendors MUST go in BDR `operations`.
110
110
 
111
- 13. `operations` vs `governance` for workforce and sustainability decisions (BDR): `operations` covers discretionary workforce policies. `governance` covers legally or regulatorily mandated obligations. Remote work policy MUST go in `operations`; labor law compliance and ESG regulatory reporting MUST go in `governance`. Voluntary sustainability targets MUST go in `operations`; mandatory ESG disclosure or regulatory sustainability compliance MUST go in `governance`.
111
+ 15. `operations` vs `governance` for workforce and sustainability decisions (BDR): `operations` covers discretionary workforce policies. `governance` covers legally or regulatorily mandated obligations. Remote work policy MUST go in `operations`; labor law compliance and ESG regulatory reporting MUST go in `governance`. Voluntary sustainability targets MUST go in `operations`; mandatory ESG disclosure or regulatory sustainability compliance MUST go in `governance`.
112
112
 
113
- 14. `product` vs `operations` for customer-facing process decisions (BDR): `product` covers the policy for what is offered, how it is sold, and how it is communicated. `operations` covers the execution process to deliver on those policies. A sales commission structure MUST go in `product`; the operational workflow for onboarding a new customer MUST go in `operations`.
113
+ 16. `product` vs `operations` for customer-facing process decisions (BDR): `product` covers the policy for what is offered, how it is sold, and how it is communicated. `operations` covers the execution process to deliver on those policies. A sales commission structure MUST go in `product`; the operational workflow for onboarding a new customer MUST go in `operations`.
114
114
 
115
- 15. Single policy spanning multiple subjects — mandatory split: When one concern spans two subjects, authors MUST NOT merge them. Create two separate documents — one per subject — linked bidirectionally in each References section. Examples: GDPR erasure → one BDR `governance` (legal obligation) + one BDR `operations` (deletion workflow); DR → one BDR `governance` (DR policy) + one BDR `operations` (drill procedure).
115
+ 17. Single policy spanning multiple subjects — mandatory split: When one concern spans two subjects, authors MUST NOT merge them. Create two separate documents — one per subject — linked bidirectionally in each References section. Examples: GDPR erasure → one BDR `governance` (legal obligation) + one BDR `operations` (deletion workflow); DR → one BDR `governance` (DR policy) + one BDR `operations` (drill procedure).
116
116
 
117
- 16. ADR vs EDR for the same topic — abstraction-level test: If the decision answers *"what to adopt or how systems relate structurally"* → ADR. If it answers *"how to implement or configure that choice"* → EDR. When a concern spans both levels, create one ADR and one EDR in the same-named subject. Examples: error budget policy MUST go in ADR `operations`; SLO measurement implementation MUST go in EDR `operations`. Chaos engineering tool adoption MUST go in ADR `platform`; the chaos runbook standards MUST go in EDR `operations`.
117
+ 18. ADR vs EDR for the same topic — abstraction-level test: If the decision answers *"what to adopt or how systems relate structurally"* → ADR. If it answers *"how to implement or configure that choice"* → EDR. When a concern spans both levels, create one ADR and one EDR in the same-named subject. Examples: error budget policy MUST go in ADR `operations`; SLO measurement implementation MUST go in EDR `operations`. Chaos engineering tool adoption MUST go in ADR `platform`; the chaos runbook standards MUST go in EDR `operations`.
118
118
 
119
- 17. Security infrastructure in ADR — `platform` vs `governance`: If the security technology primarily *provides* a runtime capability other systems depend on (KMS, VPN, network segmentation, certificate authority, PAM tooling) → `platform`. If it primarily *mandates, restricts, or audits* behaviour as a compliance control (encryption baseline, mandatory access review, data classification enforcement) → `governance`. When the same security concern yields both a capability adoption and an enforcement obligation, apply tiebreaker 15.
119
+ 19. Security infrastructure in ADR — `platform` vs `governance`: If the security technology primarily *provides* a runtime capability other systems depend on (KMS, VPN, network segmentation, certificate authority, PAM tooling) → `platform`. If it primarily *mandates, restricts, or audits* behaviour as a compliance control (encryption baseline, mandatory access review, data classification enforcement) → `governance`. When the same security concern yields both a capability adoption and an enforcement obligation, apply tiebreaker 17.
120
120
 
121
- 18. Runtime infrastructure always goes to `platform`: Any technology adopted as a runtime enabling capability — regardless of whether it also stores data (cache, message bus, search runtime, workflow engine, session store) — MUST go in `platform`. Place a technology in `data` only when it is selected for data storage, schema management, query patterns, or data lifecycle with no infrastructure runtime role. When both roles are present, apply tiebreaker 15. Examples: Redis as cache → ADR `platform`; ORM patterns → EDR `data`. Elasticsearch as search runtime → ADR `platform`; index schema standards → EDR `data`.
121
+ 20. Runtime infrastructure always goes to `platform`: Any technology adopted as a runtime enabling capability — regardless of whether it also stores data (cache, message bus, search runtime, workflow engine, session store) — MUST go in `platform`. Place a technology in `data` only when it is selected for data storage, schema management, query patterns, or data lifecycle with no infrastructure runtime role. When both roles are present, apply tiebreaker 17. Examples: Redis as cache → ADR `platform`; ORM patterns → EDR `data`. Elasticsearch as search runtime → ADR `platform`; index schema standards → EDR `data`.
122
122
 
123
- 19. BDR `finance` vs `governance` — obligation vs strategy: If the financial decision is driven by external legal, regulatory, or contractual obligation (IFRS 15, tax law, banking capital rules, mandatory insurance) → `governance`. If it is a discretionary organizational financial decision (budget process, investment thresholds, pricing approval, revenue sharing, ESOP design) → `finance`.
123
+ 21. BDR `finance` vs `governance` — obligation vs strategy: If the financial decision is driven by external legal, regulatory, or contractual obligation (IFRS 15, tax law, banking capital rules, mandatory insurance) → `governance`. If it is a discretionary organizational financial decision (budget process, investment thresholds, pricing approval, revenue sharing, ESOP design) → `finance`.
124
124
 
125
125
  22. Delivery pipeline vs production behavior — `platform` vs `operations`: Any decision governing what happens before or at deployment (build, test, package, deploy, canary rollout, blue-green switch) MUST go in `platform`. Any decision governing runtime behaviour after a successful deployment (monitoring, alerting, rollback trigger, incident response, SLO measurement) MUST go in `operations`. Sharpens tiebreaker 1: blue-green strategy → `platform`; rollback trigger and procedure → `operations`.
126
126
 
@@ -128,3 +128,4 @@ When a document could plausibly fit more than one subject, authors MUST apply th
128
128
 
129
129
  - [_core-adr-policy-001 - XDRS core](001-xdrs-core.md) — Framework structure, scopes, types, and subject folder placement
130
130
  - [_core-adr-policy-002 - Policy standards](002-policy-standards.md) — How to write Policy documents
131
+ - [_core-adr-policy-017 - Policy numbering ranges](017-policy-numbering-ranges.md) — Reserved number block per subject
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: _core-adr-policy-017-policy-numbering-ranges
3
+ description: Defines numeric block ranges per subject for policy numbering. Use when assigning a number to a new policy to ensure the number reflects the policy's subject.
4
+ apply-to: All XDRS scopes and policy numbers
5
+ valid-from: 2026-07-09
6
+ ---
7
+
8
+ # _core-adr-policy-017: Policy numbering ranges
9
+
10
+ ## Context and Problem Statement
11
+
12
+ As a scope grows across multiple subjects, doing only sequential numbering alone gives no indication of the subject, making browsing harder and independent authoring more prone to gaps that could have been intentional range separators.
13
+
14
+ How should policy numbers be allocated so that related policies are naturally grouped and the subject is visible from the number alone?
15
+
16
+ ## Decision Outcome
17
+
18
+ **Reserve fixed 100-number blocks per subject, with an upper overflow range for exhausted blocks**
19
+
20
+ Allocating one 100-number block per subject groups related policies in any sequential listing, makes subject membership visible from the number alone, and provides a clear path when a block runs out.
21
+
22
+ ### Details
23
+
24
+ #### 01-numbering-blocks-per-subject
25
+
26
+ Policies within a scope+type MUST be assigned numbers within the block reserved for their subject according to the table below. Each block covers exactly 100 numbers. Subjects that are not valid for a given type leave their block empty and unused.
27
+
28
+ | Block | Subject | Types |
29
+ |----------|---------------|--------------------|
30
+ | 001–100 | `principles` | ADR, BDR, EDR |
31
+ | 101–200 | `application` | ADR, EDR |
32
+ | 201–300 | `data` | ADR, EDR |
33
+ | 301–400 | `platform` | ADR, EDR |
34
+ | 401–500 | `operations` | ADR, BDR, EDR |
35
+ | 501–600 | `governance` | ADR, BDR, EDR |
36
+ | 601–700 | `product` | BDR |
37
+ | 701–800 | `finance` | BDR |
38
+ | 801–900 | *(reserved)* | future subjects |
39
+ | 901–999 | overflow | see rule 03 |
40
+
41
+ #### 02-next-available-number-within-block
42
+
43
+ The subject block rules in rule 01 apply to all policies, including existing ones. Any policy whose number falls outside its subject's reserved block MUST be renumbered to the lowest available number within the correct block. Authors assigning a number to a new policy MUST use the lowest available number within the subject's reserved block. Numbers from deleted policies MUST NOT be reused. Authors MUST NOT assign a number outside the subject's block unless that block is fully exhausted (see rule 03).
44
+
45
+ #### 03-overflow-range
46
+
47
+ When all 100 slots in a subject's block are exhausted, new policies for that subject MUST be placed in the overflow range (901–999), taking the next available number. The policy's subject folder governs placement; the number alone does not determine the subject for overflow policies.
48
+
49
+ #### 04-policy-numbers-must-comply
50
+
51
+ A policy's number MUST comply with the subject block rules in rule 01. Any policy with a non-compliant number MUST be renumbered to the correct block unless `freeze-reference: true` is set in the policy's frontmatter (see rule 06). Renumbering MUST update the filename, the `name` frontmatter field, the document heading, and all references to the old number in indexes, links, and citations.
52
+
53
+ #### 05-reserved-gap-must-not-be-assigned
54
+
55
+ Numbers in the reserved gap (801–900) MUST NOT be assigned to any policy. They are reserved for future subject additions defined by a future update to this policy.
56
+
57
+ #### 06-freeze-reference-exemption
58
+
59
+ When `freeze-reference: true` is set in a policy's frontmatter, numbering range compliance checks for that policy MUST be treated as exempt. The full exemption semantics are defined in [`_core-adr-policy-002-policy-standards.01-freeze-reference-exemption`](002-policy-standards.md).
60
+
61
+ ## References
62
+
63
+ - [`_core-adr-policy-001`](001-xdrs-core.md) — XDRS core framework: numbering and folder structure
64
+ - [`_core-adr-policy-002`](002-policy-standards.md) — Policy standards: freeze-reference exemption semantics
65
+ - [`_core-adr-policy-016`](016-policy-subjects.md) — Policy subjects: allowed subjects and taxonomy per type
@@ -42,7 +42,9 @@ Consult `001-xdrs-core` while making each choice in this phase. The summaries be
42
42
  When type, scope, or subject cannot be confidently inferred, ask the user a clarifying question before proceeding. Ask one question at a time and wait for the answer; follow up if the response introduces new ambiguity.
43
43
 
44
44
  **Policy ID** — format: `[scope]-[type]-[next available number]`
45
- - Scan `.xdrs/[scope]/[type]/` for the highest existing number in that scope+type and increment by 1.
45
+ - Read `_core-adr-policy-017` ([017-policy-numbering-ranges.md](../../017-policy-numbering-ranges.md)) to identify the 100-number block reserved for the chosen subject (e.g. `principles` 001–100, `application` 101–200).
46
+ - Scan `.xdrs/[scope]/[type]/[subject]/` for all existing numbers within that block and use the lowest number in the block that has not yet been assigned.
47
+ - Use the overflow range (901–999) only when all 100 slots in the subject's block are exhausted, taking the next available number there.
46
48
  - Never reuse numbers from deleted Policies.
47
49
 
48
50
  ### Phase 3: Choose the Title
@@ -45,3 +45,4 @@ Read the full content of the skill file for the inferred type, then follow all i
45
45
  - MUST follow all phases of the target skill from Phase 1 onward; do not skip any phase.
46
46
  - MUST NOT create documents of a type not listed in the routing table above.
47
47
  - When routing to the Policy skill, MUST also read `_core-adr-policy-016` (`.xdrs/_core/adrs/principles/016-policy-subjects.md`) before choosing a subject — it contains the allowed subject list, descriptions, and disambiguation tiebreaker rules.
48
+ - When routing to the Policy skill, MUST also read `_core-adr-policy-017` (`.xdrs/_core/adrs/principles/017-policy-numbering-ranges.md`) before choosing a policy number — it defines the subject-based block ranges that determine valid numbers for each subject.
package/lib/lint.js CHANGED
@@ -25,7 +25,7 @@ const SUBJECT_ARTIFACT_DIRS = new Set(['skills', 'articles', 'researches', 'plan
25
25
  const RESOURCE_DIR_NAME = '.assets';
26
26
  const SKILL_PACKAGE_OPTIONAL_DIRS = new Set(['scripts', 'references', RESOURCE_DIR_NAME]);
27
27
 
28
- const POLICY_ALLOWED_FRONTMATTER_KEYS = new Set(['name', 'description', 'apply-to', 'valid-from', 'license', 'metadata']);
28
+ const POLICY_ALLOWED_FRONTMATTER_KEYS = new Set(['name', 'description', 'apply-to', 'valid-from', 'license', 'metadata', 'freeze-reference']);
29
29
  const SKILL_ALLOWED_FRONTMATTER_KEYS = new Set(['name', 'description', 'license', 'metadata', 'compatibility', 'allowed-tools']);
30
30
  const SCOPE_INDEX_ALLOWED_FRONTMATTER_KEYS = new Set(['scope-type', 'name', 'description', 'apply-to', 'valid-from', 'license', 'metadata', 'follows', 'related-scopes']);
31
31
  const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.bmp']);
@@ -37,6 +37,21 @@ const ARTICLE_MAX_WORDS = 8000;
37
37
  const RESEARCH_MAX_WORDS = 5000;
38
38
  const SKILL_MAX_WORDS = 6500;
39
39
 
40
+ // Subject number block ranges per _core-adr-policy-017
41
+ const SUBJECT_NUMBER_RANGES = {
42
+ principles: [1, 100],
43
+ application: [101, 200],
44
+ data: [201, 300],
45
+ platform: [301, 400],
46
+ operations: [401, 500],
47
+ governance: [501, 600],
48
+ product: [601, 700],
49
+ finance: [701, 800],
50
+ };
51
+ const RESERVED_NUMBER_RANGE_MIN = 801;
52
+ const RESERVED_NUMBER_RANGE_MAX = 900;
53
+ const OVERFLOW_RANGE_MIN = 901;
54
+
40
55
  const NORMATIVE_KEYWORDS_RE = /\bMUST NOT\b|\bMUST\b|\bSHOULD NOT\b|\bSHOULD\b|\bMAY\b|\bREQUIRED\b|\bOPTIONAL\b/;
41
56
  const NORMATIVE_EMPHASIS_RE = /(\*{1,2}|_{1,2})(MUST NOT|SHOULD NOT|MUST|SHOULD|MAY|REQUIRED|OPTIONAL|always|never|mandatory|recommended|advised|preferably|possibly|optionally)(\*{1,2}|_{1,2})/i;
42
57
 
@@ -69,6 +84,12 @@ function runLintCli(args) {
69
84
  const targetPath = pathArgs[0] || '.';
70
85
  const result = lintWorkspace(targetPath, { ignoreExternal: !all });
71
86
 
87
+ if (result.warnings.length > 0) {
88
+ for (const warning of result.warnings) {
89
+ console.warn(`- [warning] ${warning}`);
90
+ }
91
+ }
92
+
72
93
  if (result.errors.length === 0) {
73
94
  console.log(`Lint passed for ${toDisplayPath(result.xdrsRoot)}`);
74
95
  if (result.readOnlyScopes.size > 0) {
@@ -110,11 +131,12 @@ function lintWorkspace(targetPath, options = {}) {
110
131
  const resolvedTarget = path.resolve(targetPath);
111
132
  const xdrsRoot = resolveXdrsRoot(resolvedTarget);
112
133
  const errors = [];
134
+ const warnings = [];
113
135
  const readOnlyScopes = new Set();
114
136
 
115
137
  if (!existsDirectory(xdrsRoot)) {
116
138
  errors.push(`Missing XDRS root directory: ${toDisplayPath(xdrsRoot)} [_core-adr-policy-001]`);
117
- return { xdrsRoot, errors, readOnlyScopes };
139
+ return { xdrsRoot, errors, warnings, readOnlyScopes };
118
140
  }
119
141
 
120
142
  const repoRoot = path.dirname(xdrsRoot);
@@ -158,7 +180,7 @@ function lintWorkspace(targetPath, options = {}) {
158
180
  }
159
181
 
160
182
  for (const scopeEntry of scopeEntries) {
161
- lintScopeDirectory(xdrsRoot, scopeEntry.name, errors, actualTypeIndexes, ignoreExternal, effectiveExternalScopes, knownScopeTypes, readOnlyScopes);
183
+ lintScopeDirectory(xdrsRoot, scopeEntry.name, errors, warnings, actualTypeIndexes, ignoreExternal, effectiveExternalScopes, knownScopeTypes, readOnlyScopes);
162
184
  }
163
185
 
164
186
  const rootIndexPath = path.join(xdrsRoot, 'index.md');
@@ -168,7 +190,7 @@ function lintWorkspace(targetPath, options = {}) {
168
190
  lintRootIndex(rootIndexPath, xdrsRoot, actualTypeIndexes, errors, effectiveExternalScopes);
169
191
  }
170
192
 
171
- return { xdrsRoot, errors, readOnlyScopes };
193
+ return { xdrsRoot, errors, warnings, readOnlyScopes };
172
194
  }
173
195
 
174
196
  function lintRootIndex(rootIndexPath, xdrsRoot, actualTypeIndexes, errors, externalScopes = new Set()) {
@@ -495,7 +517,7 @@ function lintScopeIndexFrontmatter(scopeIndexPath, scopeName, errors, xdrsRoot,
495
517
  }
496
518
  }
497
519
 
498
- function lintScopeDirectory(xdrsRoot, scopeName, errors, actualTypeIndexes, ignoreExternal, externalScopes, knownScopeTypes = new Map(), readOnlyScopes = new Set()) {
520
+ function lintScopeDirectory(xdrsRoot, scopeName, errors, warnings, actualTypeIndexes, ignoreExternal, externalScopes, knownScopeTypes = new Map(), readOnlyScopes = new Set()) {
499
521
  const scopePath = path.join(xdrsRoot, scopeName);
500
522
 
501
523
  if (ignoreExternal && externalScopes.has(scopeName)) {
@@ -515,7 +537,7 @@ function lintScopeDirectory(xdrsRoot, scopeName, errors, actualTypeIndexes, igno
515
537
  errors.push(`Unexpected directory under scope ${scopeName}: ${toDisplayPath(entryPath)} [_core-adr-policy-001]`);
516
538
  continue;
517
539
  }
518
- lintTypeDirectory(xdrsRoot, scopeName, entry.name, errors, actualTypeIndexes, externalScopes);
540
+ lintTypeDirectory(xdrsRoot, scopeName, entry.name, errors, warnings, actualTypeIndexes, externalScopes);
519
541
  const typeIndexPath = path.join(entryPath, 'index.md');
520
542
  if (existsFile(typeIndexPath)) {
521
543
  typeIndexesInScope.push(typeIndexPath);
@@ -567,7 +589,7 @@ function lintScopeLocalStandards(xdrsRoot, scopeName, errors) {
567
589
  }
568
590
  }
569
591
 
570
- function lintTypeDirectory(xdrsRoot, scopeName, typeName, errors, actualTypeIndexes, externalScopes = new Set()) {
592
+ function lintTypeDirectory(xdrsRoot, scopeName, typeName, errors, warnings, actualTypeIndexes, externalScopes = new Set()) {
571
593
  const typePath = path.join(xdrsRoot, scopeName, typeName);
572
594
  const indexPath = path.join(typePath, 'index.md');
573
595
  const xdrsNumbers = new Map();
@@ -590,11 +612,15 @@ function lintTypeDirectory(xdrsRoot, scopeName, typeName, errors, actualTypeInde
590
612
  }
591
613
 
592
614
  if (!ALLOWED_SUBJECTS[typeName].has(entry.name)) {
593
- errors.push(`Invalid subject folder for ${typeName}: ${toDisplayPath(entryPath)} [_core-adr-policy-001]`);
594
- continue;
615
+ if (hasAnyNonFrozenPolicy(entryPath)) {
616
+ errors.push(`Invalid subject folder for ${typeName}: ${toDisplayPath(entryPath)} [_core-adr-policy-001]`);
617
+ continue;
618
+ }
619
+ // All policies are frozen: emit a warning and still process for content-level checks.
620
+ warnings.push(`Invalid subject folder for ${typeName}: ${toDisplayPath(entryPath)} [_core-adr-policy-001] [exempt: freeze-reference]`);
595
621
  }
596
622
 
597
- artifacts.push(...lintSubjectDirectory(xdrsRoot, scopeName, typeName, entry.name, xdrsNumbers, errors, externalScopes));
623
+ artifacts.push(...lintSubjectDirectory(xdrsRoot, scopeName, typeName, entry.name, xdrsNumbers, errors, warnings, externalScopes));
598
624
  }
599
625
 
600
626
  if (existsFile(indexPath)) {
@@ -602,7 +628,7 @@ function lintTypeDirectory(xdrsRoot, scopeName, typeName, errors, actualTypeInde
602
628
  }
603
629
  }
604
630
 
605
- function lintSubjectDirectory(xdrsRoot, scopeName, typeName, subjectName, xdrsNumbers, errors, externalScopes = new Set()) {
631
+ function lintSubjectDirectory(xdrsRoot, scopeName, typeName, subjectName, xdrsNumbers, errors, warnings, externalScopes = new Set()) {
606
632
  const subjectPath = path.join(xdrsRoot, scopeName, typeName, subjectName);
607
633
  const artifacts = [];
608
634
  const entries = safeReadDir(subjectPath, errors, `read subject directory ${scopeName}/${typeName}/${subjectName}`);
@@ -641,7 +667,7 @@ function lintSubjectDirectory(xdrsRoot, scopeName, typeName, subjectName, xdrsNu
641
667
  }
642
668
 
643
669
  artifacts.push(entryPath);
644
- lintXdrsElementFile(xdrsRoot, scopeName, typeName, entryPath, xdrsNumbers, errors, externalScopes);
670
+ lintXdrsElementFile(xdrsRoot, scopeName, typeName, subjectName, entryPath, xdrsNumbers, errors, warnings, externalScopes);
645
671
  }
646
672
 
647
673
  const subjectAssetsDir = path.join(subjectPath, RESOURCE_DIR_NAME);
@@ -651,7 +677,7 @@ function lintSubjectDirectory(xdrsRoot, scopeName, typeName, subjectName, xdrsNu
651
677
  return artifacts;
652
678
  }
653
679
 
654
- function lintXdrsElementFile(xdrsRoot, scopeName, typeName, filePath, xdrsNumbers, errors, externalScopes = new Set()) {
680
+ function lintXdrsElementFile(xdrsRoot, scopeName, typeName, subjectName, filePath, xdrsNumbers, errors, warnings, externalScopes = new Set()) {
655
681
  const baseName = path.basename(filePath);
656
682
  const match = baseName.match(NUMBERED_FILE_RE);
657
683
  if (!match) {
@@ -671,15 +697,19 @@ function lintXdrsElementFile(xdrsRoot, scopeName, typeName, filePath, xdrsNumber
671
697
  }
672
698
 
673
699
  const content = fs.readFileSync(filePath, 'utf8');
700
+ lintPolicyNumberRange(number, subjectName, content, filePath, errors, warnings);
701
+ const { freezeReference } = extractFrontmatter(content);
674
702
  const expectedHeader = `# ${scopeName}-${TYPE_TO_ID[typeName]}-${number}:`;
675
703
  const firstLine = firstNonEmptyLine(stripFrontmatter(content));
676
- if (!firstLine.startsWith(expectedHeader)) {
704
+ if (!freezeReference && !firstLine.startsWith(expectedHeader)) {
677
705
  errors.push(`Policy title must start with "${expectedHeader}": ${toDisplayPath(filePath)} [_core-adr-policy-002]`);
706
+ } else if (freezeReference && !firstLine.startsWith(expectedHeader)) {
707
+ warnings.push(`Policy title must start with "${expectedHeader}": ${toDisplayPath(filePath)} [_core-adr-policy-002] [exempt: freeze-reference]`);
678
708
  }
679
709
 
680
710
  const expectedName = extractExpectedXdrsNameFromHeading(firstLine)
681
711
  || `${scopeName}-${TYPE_TO_ID[typeName]}-${number}-${match[2]}`;
682
- lintXdrsElementFrontmatter(content, expectedName, filePath, errors);
712
+ lintXdrsElementFrontmatter(content, expectedName, filePath, errors, warnings, freezeReference);
683
713
  lintStructuredRuleBlocks(content, filePath, errors);
684
714
  lintRequiredSections(content, filePath, XDRS_REQUIRED_SECTIONS, 'Policy', errors);
685
715
  lintNoEmojis(content, filePath, 'Policy', errors);
@@ -712,7 +742,7 @@ function slugifyTitle(title) {
712
742
  .replace(/--+/g, '-');
713
743
  }
714
744
 
715
- function lintXdrsElementFrontmatter(content, expectedName, filePath, errors) {
745
+ function lintXdrsElementFrontmatter(content, expectedName, filePath, errors, warnings, freezeReference = false) {
716
746
  const fm = extractFrontmatter(content);
717
747
  if (!fm.present) {
718
748
  errors.push(`Policy must start with a YAML frontmatter block: ${toDisplayPath(filePath)} [_core-adr-policy-002]`);
@@ -722,7 +752,8 @@ function lintXdrsElementFrontmatter(content, expectedName, filePath, errors) {
722
752
  errors.push(`Policy frontmatter must include a non-empty name field: ${toDisplayPath(filePath)} [_core-adr-policy-002]`);
723
753
  } else {
724
754
  if (fm.name !== expectedName) {
725
- errors.push(`Policy frontmatter name must be "${expectedName}": ${toDisplayPath(filePath)} [_core-adr-policy-002]`);
755
+ const msg = `Policy frontmatter name must be "${expectedName}": ${toDisplayPath(filePath)} [_core-adr-policy-002]`;
756
+ if (freezeReference) { warnings.push(`${msg} [exempt: freeze-reference]`); } else { errors.push(msg); }
726
757
  }
727
758
  if (fm.name.endsWith('-')) {
728
759
  errors.push(`Policy frontmatter name must not end with a hyphen: ${toDisplayPath(filePath)} [_core-adr-policy-002]`);
@@ -1017,6 +1048,40 @@ function lintPlansDirectory(xdrsRoot, scopeName, typeName, subjectName, plansPat
1017
1048
  return artifacts;
1018
1049
  }
1019
1050
 
1051
+ /**
1052
+ * Returns true when at least one policy file in dirPath does NOT have freeze-reference: true.
1053
+ * Used to decide whether an invalid-subject-folder error should be reported.
1054
+ */
1055
+ function hasAnyNonFrozenPolicy(dirPath) {
1056
+ let entries;
1057
+ try { entries = fs.readdirSync(dirPath, { withFileTypes: true }); } catch { return false; }
1058
+ for (const entry of entries) {
1059
+ if (!entry.isFile() || !NUMBERED_FILE_RE.test(entry.name)) continue;
1060
+ let content;
1061
+ try { content = fs.readFileSync(path.join(dirPath, entry.name), 'utf8'); } catch { return true; }
1062
+ const fm = extractFrontmatter(content);
1063
+ if (!fm.freezeReference) return true;
1064
+ }
1065
+ return false;
1066
+ }
1067
+
1068
+ function lintPolicyNumberRange(number, subjectName, content, filePath, errors, warnings) {
1069
+ const fm = extractFrontmatter(content);
1070
+ const num = parseInt(number, 10);
1071
+ if (num >= RESERVED_NUMBER_RANGE_MIN && num <= RESERVED_NUMBER_RANGE_MAX) {
1072
+ const msg = `Policy number ${number} is in the reserved range (801–900) which MUST NOT be assigned: ${toDisplayPath(filePath)} [_core-adr-policy-017.05-reserved-gap-must-not-be-assigned]`;
1073
+ if (fm.freezeReference) { warnings.push(`${msg} [exempt: freeze-reference]`); } else { errors.push(msg); }
1074
+ return;
1075
+ }
1076
+ if (num < OVERFLOW_RANGE_MIN) {
1077
+ const range = SUBJECT_NUMBER_RANGES[subjectName];
1078
+ if (range && (num < range[0] || num > range[1])) {
1079
+ const msg = `Policy number ${number} is outside the ${range[0]}–${range[1]} block reserved for subject "${subjectName}": ${toDisplayPath(filePath)} [_core-adr-policy-017.01-numbering-blocks-per-subject]`;
1080
+ if (fm.freezeReference) { warnings.push(`${msg} [exempt: freeze-reference]`); } else { errors.push(msg); }
1081
+ }
1082
+ }
1083
+ }
1084
+
1020
1085
  function lintNoEmojis(content, filePath, docType, errors) {
1021
1086
  const lines = content.split(/\r?\n/);
1022
1087
  const ignoredLines = findIgnoredMarkdownLines(lines);
@@ -1401,6 +1466,7 @@ function extractFrontmatter(content) {
1401
1466
  const keyMatch = line.match(/^([a-zA-Z][a-zA-Z0-9_-]*):\s*/);
1402
1467
  if (keyMatch) topLevelKeys.push(keyMatch[1]);
1403
1468
  }
1469
+ const freezeReferenceMatch = block.match(/^freeze-reference:\s*(.+)$/m);
1404
1470
  const descriptionText = extractDescriptionText(block);
1405
1471
  return {
1406
1472
  present: true,
@@ -1409,6 +1475,7 @@ function extractFrontmatter(content) {
1409
1475
  descriptionText,
1410
1476
  validFrom: validFromMatch ? validFromMatch[1].trim() : undefined,
1411
1477
  appliedTo: appliedToMatch ? appliedToMatch[1].trim() : undefined,
1478
+ freezeReference: freezeReferenceMatch ? freezeReferenceMatch[1].trim() === 'true' : false,
1412
1479
  topLevelKeys,
1413
1480
  };
1414
1481
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xdrs-core",
3
- "version": "0.39.0",
3
+ "version": "0.40.0",
4
4
  "description": "A framework to structure, compile and distribute Architectural (ADR), Business (BDR), and Engineering (EDR) decision records contents so that AI agents and humans can reliably find and use them with hierarchical scopes and controlled rollout in the format of distributable versioned packages.",
5
5
  "repository": {
6
6
  "type": "git",