From e931c52b3f0c3b7ce4c2c605ec9d92975ed23c83 Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 31 Aug 2026 05:16:51 +0900 Subject: [PATCH 1/7] docs(prd): restore unique occupational authority --- docs/product-requirements.md | 140 +++++-------------------- docs/product-technical-gap-baseline.md | 21 ++++ tests/test_documentation_hygiene.py | 28 +++++ 3 files changed, 74 insertions(+), 115 deletions(-) diff --git a/docs/product-requirements.md b/docs/product-requirements.md index a8b741521..8de47c004 100644 --- a/docs/product-requirements.md +++ b/docs/product-requirements.md @@ -84,66 +84,11 @@ and lookup round-trip isolation are enforced by `tests/test_worker_function_taxonomy.py`; `tests/test_ontology.py` continues to pass unchanged. -### PRD-FR-2B — Evidence-bound occupational constructs - -- Keep cognitive abilities, work styles, work activities, affective - reactions, and performance behaviors as non-equivalent construct classes - (ADR 0248). FJA worker functions remain separate. -- Reuse official external identifiers and source-published relationships; - never infer a DPT-to-psychology crosswalk or relabel work style as affect. -- Bind a construct to record content only through a provenance-bearing, - evidence-cited assertion. Do not promote record evidence to a person trait, - score, causal effect, or job requirement. - -Acceptance: SHACL rejects incomplete assertions; ontology tests prohibit FJA -equivalence and require exact Post/evidence/PROV statement structure. ADR 0249 -adds normalized, semantic-unit-bound persistence and an authorized Post-detail -projection. ADR 0250 synchronizes all official O*NET cognitive-ability, -work-style, and work-activity Content Model elements into that versioned -registry without importing ratings. Search, graph navigation, extraction, and -UI remain unavailable until their separate ADR acceptance. ADR 0253 adds -catalog-bound semantic-unit extraction through contextual-orchestrator's -multi-agent conduct path; exact offered IRIs and verbatim spans are required, -and a digest-bound run record distinguishes a supported empty result from an -unavailable provider. ADR 0254 adds the authorized Post-detail evidence review -surface and honest complete, processing, and unavailable states. ADR 0255 -projects assertion-backed constructs into the existing ABAC-filtered ontology -neighborhood without duplicating graph storage or promoting truth. ADR 0257 -adds authorized catalog-label search: reviewers type an official O*NET label -and open the earliest visible supporting Post. Constructs without visible -evidence stay undisclosed. Occupation ratings remain unavailable. - -### PRD-FR-2C — FJA I/O-Psychology cognitive, affective & behavioral semantic layer - -- Project the DOT/FJA Data/People/Things worker functions into their - grounded nomological network of cognitive, affective, and behavioral - I/O-Psychology constructs (ADR 0251): information processing, mental - workload, executive functioning, and appraisal; emotional labor, - burnout, engagement, psychological safety, and commitment; task, - citizenship, counterproductive, safety, proactive, adaptive, and - withdrawal behavior. -- Declare each construct with its psychological dimension and an APA 7th - literature anchor; keep `:CognitiveConstruct` / `:AffectiveConstruct` / - `:BehavioralConstruct` disjoint and validate with SHACL. -- Keep FJA-derived constructs distinct from ADR 0248's evidence-bound - O*NET-style occupational construct classes: no crosswalk, equivalence, - or implied fit is asserted. -- Carry no numeric weight: the layer is a semantic taxonomy, never a - calibrated measurement (ADR 0145 governs estimation). - -Acceptance: `tests/test_iopsy_taxonomy.py` enforces construct coverage, -literature-anchored metadata, fail-closed lookups, per-function profile -completeness, and composite-job aggregation; `tests/test_ontology_shapes.py` -validates the disjoint SHACL shapes. - - - -### PRD-FR-2B-2 — Occupational classification and worker-characteristic taxonomy +### PRD-FR-2B — Occupational classification and worker-characteristic taxonomy - Publish the 23 major groups of the 2018 Standard Occupational - Classification (the O*NET job-family grouping) with official titles - and codes verbatim, plus the four O*NET 31.0 job-zone categories with - published names and source values 2 through 5 (ADR 0245). + Classification and the four O*NET 31.0 job-zone categories with exact + published names, codes, and source values (ADR 0245). - Publish the worker-characteristic families that work-related cognition, affect, and behavior resolve into: Fleishman's four ability domains, Holland's six RIASEC interest types with the published @@ -163,56 +108,10 @@ Acceptance: completeness counts, verbatim titles, closed RIASEC vocabulary, exact published adjacency pairs, deterministic ordering, canonical namespace, and lookup round-trip isolation are enforced by `tests/test_io_taxonomy.py`; `tests/test_ontology.py` continues to pass -unchanged. - -### PRD-FR-2A — Worker-function taxonomy - -- Publish the DOT/FJA Data/People/Things worker functions (24 concepts, - official definitions verbatim) in the canonical ontology namespace - (ADR 0232), each with its definitional ordinal rank. Do not infer a - DOT-to-O*NET or Fleishman crosswalk that the authorities do not publish. -- Expose the taxonomy through a deterministic application read model with - fail-closed lookups; an absent function is an honest unknown. -- Carry no numeric weight from the taxonomy: ranks are scale positions, - never calibrated weights. +unchanged. Minor, broad, and detailed SOC levels and the complete O*NET +Content Model remain unavailable until a governing ADR accepts their pinned +source and publication contract. -Acceptance: completeness, full verbatim definitions, deterministic ordering, -and lookup round-trip isolation are enforced by -`tests/test_worker_function_taxonomy.py`; `tests/test_ontology.py` -continues to pass unchanged. - -### PRD-FR-2B — Occupational classification and worker-characteristic taxonomy - -- Publish all four levels of the 2018 Standard Occupational Classification: - 23 major groups, 98 minor groups, 459 broad occupations, and 867 detailed - occupations with exact source parents, titles, and codes (ADR 0252), plus - the four O*NET 31.0 job-zone categories with - published names and source values 2 through 5 (ADR 0245). -- Publish the worker-characteristic families that work-related - cognition, affect, and behavior resolve into: Fleishman's four ability - domains, Holland's six RIASEC interest types with the published - hexagonal adjacency relation, the six explicitly legacy O*NET work-value - clusters, and - the seven higher-order dimensions of the revised O*NET Work Styles - structure. -- Publish all 3,006 O*NET 31.0 Content Model Reference elements with exact - identifiers, names, descriptions, and source-defined outline parents - (ADR 0264). Treat the six roots and 18 second-level branches as navigation - classes, never occupation ratings, person traits, scores, or weights. -- Declare typed derivation properties from classifications to - characteristics but assert no instance binding; binding requires a - versioned released source profile imported with provenance in its own - decision. -- Expose everything through a deterministic application read model with - fail-closed lookups; carry no numeric importance or level rating from - any occupational profile. - -Acceptance: completeness counts, verbatim titles, closed RIASEC -vocabulary, exact published adjacency pairs, deterministic ordering, -canonical namespace, and lookup round-trip isolation are enforced by -`tests/test_io_taxonomy.py`, `tests/test_soc_2018_hierarchy.py`, and -`tests/test_onet_content_model.py`; -`tests/test_ontology.py` continues to pass unchanged. ### PRD-FR-2C — Evidence-bound occupational constructs - Keep cognitive abilities, work styles, work activities, affective @@ -220,18 +119,30 @@ canonical namespace, and lookup round-trip isolation are enforced by (ADR 0248). FJA worker functions remain separate. - Reuse official external identifiers and source-published relationships; never infer a DPT-to-psychology crosswalk or relabel work style as affect. -- Publish the eight O*NET 31.0 Ability, Essential Skill, Transferable Skill, - and Work Style link tables to Work Activities and Work Context as 1,417 - directed, assertion-level provenance-bearing relations (ADR 0256). Treat - relevance as neither a causal effect nor a numeric weight. - Bind a construct to record content only through a provenance-bearing, evidence-cited assertion. Do not promote record evidence to a person trait, score, causal effect, or job requirement. Acceptance: SHACL rejects incomplete record assertions; ontology tests -prohibit FJA equivalence, require exact Post/evidence/PROV statement structure, -and reproduce every pinned O*NET linkage with its exact source table. Runtime -persistence and UI remain unavailable until their separate ADR acceptance. +prohibit FJA equivalence and require exact Post/evidence/PROV statement +structure. ADRs 0249, 0250, and 0253–0255 govern normalized persistence, +the pinned catalog, contextual-orchestrator extraction, review UI, and graph +projection. Unsupported O*NET linkage tables remain unavailable; Voice +combination ADR 0256 is not occupational authority. + +### PRD-FR-2K — FJA I/O-Psychology semantic layer + +- Project DOT/FJA Data/People/Things worker functions into the grounded + cognitive, affective, and behavioral I/O-Psychology construct taxonomy + accepted by ADR 0251. +- Keep cognitive, affective, and behavioral classes disjoint, literature + anchored, and distinct from evidence-bound O*NET occupational constructs. +- Carry no numeric weight or inferred crosswalk; ADR 0145 continues to govern + measurement availability. + +Acceptance: `tests/test_iopsy_taxonomy.py` enforces construct coverage, +literature metadata, fail-closed lookup, per-function profiles, and composite +job aggregation; `tests/test_ontology_shapes.py` validates disjoint shapes. ### PRD-FR-2D — Occupation-rating source observations @@ -504,7 +415,6 @@ A release claim requires one exact protected-main head that proves: - Asynchronous delivery and database-pool isolation: ADR 0204, ADR 0213. - Knowledge Graph, ontology, and provenance: ADR 0004, ADR 0011, ADR 0065, ADR 0184, ADR 0207, ADR 0222, ADR 0246, ADR 0256. - ADR 0184, ADR 0207, ADR 0222, ADR 0246. - Semantic units and retrieval: ADR 0047, ADR 0062, ADR 0102, ADR 0217. - LLM/model boundary: ADR 0070, ADR 0072, ADR 0076, ADR 0079. - Measurement: ADR 0003, ADR 0145, ADR 0200, ADR 0205. diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index b5d31877b..b7cb1c44a 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -1,5 +1,26 @@ # Product & Technical Gap Baseline +> Exact-head loop overlay: 2026-08-31 KST. Protected `main` is +> `cb187cadee5fb6c46d8a944815ccc154a1e028d1`, the merge SHA for #782. +> GitHub records no independent `APPROVED` review on #782 and its author merged +> it while exact-head required controls were not successful; this is a +> governance violation, not protected-delivery proof. Draft revert #808 is +> `1af3e53e55d7a1c8572ab514d14d06c615c7c0d0` and also lacks independent +> approval, so it remains unmerged. There are 50 open PRs and 10 open issues. +> Main-based ready PRs #771, #772, #774, and #780 retain normal squash +> auto-merge and remain blocked on an independent approval plus exact-head +> required checks. Drafts and dirty branches are candidate evidence only. +> +> Canonical remote names rechecked this cycle are +> `ContextualWisdomLab/LineageWeave`, `ContextualWisdomLab/RankWeave`, +> `ContextualWisdomLab/ThreadWeave`, `ContextualWisdomLab/disksage`, and +> `ContextualWisdomLab/TEPP`. The current PRD had repeated identifiers for +> PRD-FR-2A, PRD-FR-2B, and PRD-FR-2C plus a conflicting PRD-FR-2B-2 draft. +> The superseded copies are removed in the current candidate and a regression +> test now makes duplicate PRD identifiers fail closed. This resolves issue +> #807's authority ambiguity without changing an ADR, API, schema, model, or +> release number. + > Exact-head loop overlay: 2026-08-29 13:20 KST. Protected `main` is > `fc13acaa20adca11968238e398d4aafcf62b6cee` (v2.23.0 leftover-map > explained leftover share, #775). Open ready PRs still lack independent diff --git a/tests/test_documentation_hygiene.py b/tests/test_documentation_hygiene.py index b287d3470..e521580ec 100644 --- a/tests/test_documentation_hygiene.py +++ b/tests/test_documentation_hygiene.py @@ -9,12 +9,15 @@ _ROOT = Path(__file__).resolve().parents[1] _ADR_DIRECTORY = _ROOT / "docs" / "adr" _PRODUCT_GAP_BASELINE = _ROOT / "docs" / "product-technical-gap-baseline.md" +_PRODUCT_REQUIREMENTS = _ROOT / "docs" / "product-requirements.md" _ROLE_CATALOG_COLUMNS = ( "cataloged_team_id", "cataloged_corporate_entity_id", "cataloged_person_id", ) _ADR_NAME = re.compile(r"^(?P[0-9]{4})-.+\.md$") +_PRD_REQUIREMENT_HEADING = re.compile(r"^### (?PPRD-FR-[0-9A-Z-]+)\b", re.MULTILINE) +_PRD_ADR_REFERENCE = re.compile(r"\bADRs?\s+(?P[0-9]{4})\b") _PRIVATE_POST_IDENTIFIER = re.compile( r"(?i)" r"\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b" @@ -70,6 +73,31 @@ def test_product_gap_baseline_contains_no_private_post_identifiers() -> None: assert match is None, f"private post identifier in product-gap baseline: {match.group(0)!r}" +def test_product_requirement_identifiers_are_unique() -> None: + """Each PRD identifier names one current requirement and acceptance contract.""" + product_requirements = _PRODUCT_REQUIREMENTS.read_text(encoding="utf-8") + identifiers = _PRD_REQUIREMENT_HEADING.findall(product_requirements) + + assert identifiers, "the product requirements must contain numbered requirements" + counts = Counter(identifiers) + duplicates = sorted(identifier for identifier, count in counts.items() if count > 1) + assert duplicates == [], f"duplicate PRD requirement identifiers: {duplicates}" + + +def test_product_requirement_adr_references_exist() -> None: + """Direct ADR references in the supporting PRD resolve to normative records.""" + product_requirements = _PRODUCT_REQUIREMENTS.read_text(encoding="utf-8") + referenced_numbers = set(_PRD_ADR_REFERENCE.findall(product_requirements)) + available_numbers = { + match.group("number") + for path in _ADR_DIRECTORY.glob("*.md") + if (match := _ADR_NAME.fullmatch(path.name)) is not None + } + + missing = sorted(referenced_numbers - available_numbers) + assert missing == [], f"PRD references missing ADRs: {missing}" + + def test_fetch_persisted_summary_reads_stored_catalog_ids() -> None: """ADR 0019 / 0027: fetch must not rejoin the catalog by a non-unique name.""" From 3af2959b4dfa6594e7c8fc7cf295a8523b8ed873 Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 31 Aug 2026 05:20:51 +0900 Subject: [PATCH 2/7] fix(docs): preserve PRD and ADR traceability --- ...onal-taxonomy-in-the-published-ontology.md | 5 ---- docs/product-requirements.md | 15 +++++----- tests/test_documentation_hygiene.py | 28 +++++++++++++++++-- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/docs/adr/0245-io-occupational-taxonomy-in-the-published-ontology.md b/docs/adr/0245-io-occupational-taxonomy-in-the-published-ontology.md index a8476f33d..355337666 100644 --- a/docs/adr/0245-io-occupational-taxonomy-in-the-published-ontology.md +++ b/docs/adr/0245-io-occupational-taxonomy-in-the-published-ontology.md @@ -3,11 +3,6 @@ **Status:** Accepted **Date:** 2026-08-26 **Extends:** [ADR 0004](0004-knowledge-graph-ontology.md), [ADR 0145](0145-psychometric-channel-weight-estimation.md), [ADR 0207](0207-repository-case-ontology-namespace-canonical.md), [ADR 0232](0232-worker-function-taxonomy-in-the-published-ontology.md) -<<<<<<< HEAD -======= -**Superseded in part by:** [ADR 0252](0252-complete-2018-soc-hierarchy.md), which expands the major-group-only scheme into the complete 2018 SOC hierarchy. ->>>>>>> origin/feat/onet-rating-occupation-filter - ## Context Industrial and organizational psychology classifies work twice: once by diff --git a/docs/product-requirements.md b/docs/product-requirements.md index 8de47c004..111029581 100644 --- a/docs/product-requirements.md +++ b/docs/product-requirements.md @@ -84,7 +84,7 @@ and lookup round-trip isolation are enforced by `tests/test_worker_function_taxonomy.py`; `tests/test_ontology.py` continues to pass unchanged. -### PRD-FR-2B — Occupational classification and worker-characteristic taxonomy +### PRD-FR-2L — Occupational classification and worker-characteristic taxonomy - Publish the 23 major groups of the 2018 Standard Occupational Classification and the four O*NET 31.0 job-zone categories with exact @@ -112,7 +112,7 @@ unchanged. Minor, broad, and detailed SOC levels and the complete O*NET Content Model remain unavailable until a governing ADR accepts their pinned source and publication contract. -### PRD-FR-2C — Evidence-bound occupational constructs +### PRD-FR-2B — Evidence-bound occupational constructs - Keep cognitive abilities, work styles, work activities, affective reactions, and performance behaviors as non-equivalent construct classes @@ -125,12 +125,13 @@ source and publication contract. Acceptance: SHACL rejects incomplete record assertions; ontology tests prohibit FJA equivalence and require exact Post/evidence/PROV statement -structure. ADRs 0249, 0250, and 0253–0255 govern normalized persistence, -the pinned catalog, contextual-orchestrator extraction, review UI, and graph -projection. Unsupported O*NET linkage tables remain unavailable; Voice -combination ADR 0256 is not occupational authority. +structure. ADRs 0249, 0250, 0253, and 0255 govern normalized persistence, +the pinned catalog, contextual-orchestrator extraction, and graph projection. +The review UI remains unavailable without its own accepted ADR. Unsupported +O*NET linkage tables remain unavailable; Voice combination ADR 0256 is not +occupational authority. -### PRD-FR-2K — FJA I/O-Psychology semantic layer +### PRD-FR-2C — FJA I/O-Psychology semantic layer - Project DOT/FJA Data/People/Things worker functions into the grounded cognitive, affective, and behavioral I/O-Psychology construct taxonomy diff --git a/tests/test_documentation_hygiene.py b/tests/test_documentation_hygiene.py index e521580ec..93b23c938 100644 --- a/tests/test_documentation_hygiene.py +++ b/tests/test_documentation_hygiene.py @@ -17,7 +17,10 @@ ) _ADR_NAME = re.compile(r"^(?P[0-9]{4})-.+\.md$") _PRD_REQUIREMENT_HEADING = re.compile(r"^### (?PPRD-FR-[0-9A-Z-]+)\b", re.MULTILINE) -_PRD_ADR_REFERENCE = re.compile(r"\bADRs?\s+(?P[0-9]{4})\b") +_PRD_ADR_CLAUSE = re.compile(r"\bADRs?\s+(?P[^.;)]*)") +_ADR_NUMBER_OR_RANGE = re.compile( + r"(?P[0-9]{4})(?:\s*[–-]\s*(?P[0-9]{4}))?" +) _PRIVATE_POST_IDENTIFIER = re.compile( r"(?i)" r"\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b" @@ -87,7 +90,14 @@ def test_product_requirement_identifiers_are_unique() -> None: def test_product_requirement_adr_references_exist() -> None: """Direct ADR references in the supporting PRD resolve to normative records.""" product_requirements = _PRODUCT_REQUIREMENTS.read_text(encoding="utf-8") - referenced_numbers = set(_PRD_ADR_REFERENCE.findall(product_requirements)) + referenced_numbers: set[str] = set() + for clause in _PRD_ADR_CLAUSE.finditer(product_requirements): + for reference in _ADR_NUMBER_OR_RANGE.finditer(clause.group("references")): + start = int(reference.group("start")) + end = int(reference.group("end") or start) + referenced_numbers.update(f"{number:04d}" for number in range(start, end + 1)) + + assert {"0249", "0250", "0253", "0255"} <= referenced_numbers available_numbers = { match.group("number") for path in _ADR_DIRECTORY.glob("*.md") @@ -98,6 +108,20 @@ def test_product_requirement_adr_references_exist() -> None: assert missing == [], f"PRD references missing ADRs: {missing}" +def test_adr_product_requirement_references_exist() -> None: + """Accepted ADR traceability cannot point at a removed PRD requirement.""" + product_requirements = _PRODUCT_REQUIREMENTS.read_text(encoding="utf-8") + available_identifiers = set(_PRD_REQUIREMENT_HEADING.findall(product_requirements)) + referenced_identifiers: set[str] = set() + for path in _ADR_DIRECTORY.glob("*.md"): + referenced_identifiers.update( + re.findall(r"\bPRD-FR-[0-9A-Z-]+\b", path.read_text(encoding="utf-8")) + ) + + missing = sorted(referenced_identifiers - available_identifiers) + assert missing == [], f"ADRs reference missing PRD requirements: {missing}" + + def test_fetch_persisted_summary_reads_stored_catalog_ids() -> None: """ADR 0019 / 0027: fetch must not rejoin the catalog by a non-unique name.""" From a87c6ec96c546c214461727efedeb8e7549d0fdd Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 31 Aug 2026 06:33:24 +0900 Subject: [PATCH 3/7] test(docs): fail closed on malformed authority references --- docs/product-technical-gap-baseline.md | 3 +-- tests/test_documentation_hygiene.py | 34 +++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index b7cb1c44a..539817cf9 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -6,11 +6,10 @@ > it while exact-head required controls were not successful; this is a > governance violation, not protected-delivery proof. Draft revert #808 is > `1af3e53e55d7a1c8572ab514d14d06c615c7c0d0` and also lacks independent -> approval, so it remains unmerged. There are 50 open PRs and 10 open issues. +> approval, so it remains unmerged. There are 55 open PRs and 10 open issues. > Main-based ready PRs #771, #772, #774, and #780 retain normal squash > auto-merge and remain blocked on an independent approval plus exact-head > required checks. Drafts and dirty branches are candidate evidence only. -> > Canonical remote names rechecked this cycle are > `ContextualWisdomLab/LineageWeave`, `ContextualWisdomLab/RankWeave`, > `ContextualWisdomLab/ThreadWeave`, `ContextualWisdomLab/disksage`, and diff --git a/tests/test_documentation_hygiene.py b/tests/test_documentation_hygiene.py index 93b23c938..c92092347 100644 --- a/tests/test_documentation_hygiene.py +++ b/tests/test_documentation_hygiene.py @@ -33,6 +33,20 @@ ) +def _adr_is_current(content: str) -> bool: + """Return false only for an ADR whose own status fully retires it.""" + inline_status = re.search( + r"(?im)^(?:[-*]\s*)?(?:\*\*)?(?:decision\s+)?status(?:\*\*)?\s*:\s*(.+)$", + content, + ) + if inline_status is not None: + status = inline_status.group(1).strip().casefold() + else: + section_status = re.search(r"(?im)^## Status\s*\n\s*([^\n]+)", content) + status = section_status.group(1).strip().casefold() if section_status else "" + return not status.startswith(("retired", "superseded by")) + + def test_adr_numbers_are_unique_and_documents_are_not_placeholders() -> None: """Every committed ADR number identifies one substantive UTF-8 document.""" paths = sorted(_ADR_DIRECTORY.glob("*.md")) @@ -87,6 +101,15 @@ def test_product_requirement_identifiers_are_unique() -> None: assert duplicates == [], f"duplicate PRD requirement identifiers: {duplicates}" +def test_retired_adr_status_is_excluded_without_hiding_partial_amendments() -> None: + """Only a fully retired or superseded ADR leaves current PRD traceability.""" + assert not _adr_is_current("# ADR\n\n- Status: Superseded by ADR 0002\n") + assert not _adr_is_current("# ADR\n\n## Status\n\nRetired\n") + assert _adr_is_current( + "# ADR\n\n**Decision status:** Accepted, point 3 superseded by ADR 0002\n" + ) + + def test_product_requirement_adr_references_exist() -> None: """Direct ADR references in the supporting PRD resolve to normative records.""" product_requirements = _PRODUCT_REQUIREMENTS.read_text(encoding="utf-8") @@ -95,6 +118,9 @@ def test_product_requirement_adr_references_exist() -> None: for reference in _ADR_NUMBER_OR_RANGE.finditer(clause.group("references")): start = int(reference.group("start")) end = int(reference.group("end") or start) + assert start <= end, ( + f"descending ADR range in product requirements: {start:04d}-{end:04d}" + ) referenced_numbers.update(f"{number:04d}" for number in range(start, end + 1)) assert {"0249", "0250", "0253", "0255"} <= referenced_numbers @@ -114,9 +140,11 @@ def test_adr_product_requirement_references_exist() -> None: available_identifiers = set(_PRD_REQUIREMENT_HEADING.findall(product_requirements)) referenced_identifiers: set[str] = set() for path in _ADR_DIRECTORY.glob("*.md"): - referenced_identifiers.update( - re.findall(r"\bPRD-FR-[0-9A-Z-]+\b", path.read_text(encoding="utf-8")) - ) + content = path.read_text(encoding="utf-8") + if _adr_is_current(content): + referenced_identifiers.update( + re.findall(r"\bPRD-FR-[0-9A-Z-]+\b", content) + ) missing = sorted(referenced_identifiers - available_identifiers) assert missing == [], f"ADRs reference missing PRD requirements: {missing}" From 0b1dc95ae48e5319ca3b98f9fe54ac1d99f93b09 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 1 Sep 2026 12:12:46 +0900 Subject: [PATCH 4/7] docs(prd): preserve canonical disksage identity Align the ecosystem authority register with the protected remote name and add a documentation hygiene regression without promoting an unmerged local PRD. Signed-off-by: Codex --- docs/product-requirements.md | 2 +- tests/test_documentation_hygiene.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/product-requirements.md b/docs/product-requirements.md index 111029581..dcccd36bb 100644 --- a/docs/product-requirements.md +++ b/docs/product-requirements.md @@ -437,7 +437,7 @@ current boundary until that repository adopts one. | `ContextualWisdomLab/keyverse` | `docs/PRD.md` | Production OIDC/JWKS/identity control plane; local demo Keycloak is not Keyverse | | `ContextualWisdomLab/RankWeave` | No standalone PRD; `README.md`, `ARCHITECTURE.md` | Store-agnostic ranking/fusion dependency; caller owns channels and authorization | | `ContextualWisdomLab/ThreadWeave` | `docs/PRD.md` | Deterministic reference-thread assembly dependency; LineageWeave owns records and persistence | -| `ContextualWisdomLab/DiskSage` | No standalone PRD; `docs/superpowers/specs/2026-07-10-disksage-design.md` | Prospective storage-policy boundary; no current runtime integration | +| `ContextualWisdomLab/disksage` | No standalone PRD; `docs/superpowers/specs/2026-07-10-disksage-design.md` | Prospective storage-policy boundary; no current runtime integration | | `ContextualWisdomLab/wardnet` | No standalone PRD; `README.md`, `docs/architecture.md` | Prospective gateway/network-policy boundary; no current runtime integration | | `ContextualWisdomLab/naruon` | Scoped `docs/topic-intelligence/PRD.md` only | Owns observed calendar/email projections; LineageWeave owns commitments and combined display | | `ContextualWisdomLab/LineageWeave` | This PRD, with ADRs normative | Evidence BI/orchestration, lineage, semantic projection, API, and UI owner | diff --git a/tests/test_documentation_hygiene.py b/tests/test_documentation_hygiene.py index c92092347..39541659a 100644 --- a/tests/test_documentation_hygiene.py +++ b/tests/test_documentation_hygiene.py @@ -101,6 +101,13 @@ def test_product_requirement_identifiers_are_unique() -> None: assert duplicates == [], f"duplicate PRD requirement identifiers: {duplicates}" +def test_product_authority_register_uses_canonical_repository_names() -> None: + """Keep ecosystem repository identities aligned with their remote names.""" + content = _PRODUCT_REQUIREMENTS.read_text(encoding="utf-8") + assert "`ContextualWisdomLab/disksage`" in content + assert "ContextualWisdomLab/DiskSage" not in content + + def test_retired_adr_status_is_excluded_without_hiding_partial_amendments() -> None: """Only a fully retired or superseded ADR leaves current PRD traceability.""" assert not _adr_is_current("# ADR\n\n- Status: Superseded by ADR 0002\n") From b6b1d621a2d8e6bdfbf71f79f8a183587ec86433 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 1 Sep 2026 14:26:33 +0900 Subject: [PATCH 5/7] test(docs): reject partial ADR references Signed-off-by: Codex --- tests/test_documentation_hygiene.py | 39 +++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/tests/test_documentation_hygiene.py b/tests/test_documentation_hygiene.py index 39541659a..de2e1e2fc 100644 --- a/tests/test_documentation_hygiene.py +++ b/tests/test_documentation_hygiene.py @@ -6,6 +6,8 @@ from collections import Counter from pathlib import Path +import pytest + _ROOT = Path(__file__).resolve().parents[1] _ADR_DIRECTORY = _ROOT / "docs" / "adr" _PRODUCT_GAP_BASELINE = _ROOT / "docs" / "product-technical-gap-baseline.md" @@ -19,7 +21,8 @@ _PRD_REQUIREMENT_HEADING = re.compile(r"^### (?PPRD-FR-[0-9A-Z-]+)\b", re.MULTILINE) _PRD_ADR_CLAUSE = re.compile(r"\bADRs?\s+(?P[^.;)]*)") _ADR_NUMBER_OR_RANGE = re.compile( - r"(?P[0-9]{4})(?:\s*[–-]\s*(?P[0-9]{4}))?" + r"(?[0-9]{4})(?![0-9])" + r"(?:\s*[\u2013-]\s*(?P[0-9]{4})(?![0-9]))?" ) _PRIVATE_POST_IDENTIFIER = re.compile( r"(?i)" @@ -47,6 +50,23 @@ def _adr_is_current(content: str) -> bool: return not status.startswith(("retired", "superseded by")) +def _referenced_adr_numbers(content: str) -> set[str]: + """Return complete direct ADR references, rejecting malformed numbers.""" + referenced_numbers: set[str] = set() + for clause in _PRD_ADR_CLAUSE.finditer(content): + references = clause.group("references") + malformed = [token for token in re.findall(r"[0-9]+", references) if len(token) != 4] + if malformed: + raise ValueError(f"malformed ADR reference: {malformed[0]}") + for reference in _ADR_NUMBER_OR_RANGE.finditer(references): + start = int(reference.group("start")) + end = int(reference.group("end") or start) + if start > end: + raise ValueError(f"descending ADR range: {start:04d}-{end:04d}") + referenced_numbers.update(f"{number:04d}" for number in range(start, end + 1)) + return referenced_numbers + + def test_adr_numbers_are_unique_and_documents_are_not_placeholders() -> None: """Every committed ADR number identifies one substantive UTF-8 document.""" paths = sorted(_ADR_DIRECTORY.glob("*.md")) @@ -120,15 +140,7 @@ def test_retired_adr_status_is_excluded_without_hiding_partial_amendments() -> N def test_product_requirement_adr_references_exist() -> None: """Direct ADR references in the supporting PRD resolve to normative records.""" product_requirements = _PRODUCT_REQUIREMENTS.read_text(encoding="utf-8") - referenced_numbers: set[str] = set() - for clause in _PRD_ADR_CLAUSE.finditer(product_requirements): - for reference in _ADR_NUMBER_OR_RANGE.finditer(clause.group("references")): - start = int(reference.group("start")) - end = int(reference.group("end") or start) - assert start <= end, ( - f"descending ADR range in product requirements: {start:04d}-{end:04d}" - ) - referenced_numbers.update(f"{number:04d}" for number in range(start, end + 1)) + referenced_numbers = _referenced_adr_numbers(product_requirements) assert {"0249", "0250", "0253", "0255"} <= referenced_numbers available_numbers = { @@ -141,6 +153,13 @@ def test_product_requirement_adr_references_exist() -> None: assert missing == [], f"PRD references missing ADRs: {missing}" +def test_product_requirement_adr_references_reject_partial_numbers() -> None: + """A valid sibling reference cannot hide a malformed direct ADR number.""" + for malformed in ("ADR 02490, ADR 0250", "ADR 249, ADR 0250"): + with pytest.raises(ValueError, match="malformed ADR reference"): + _referenced_adr_numbers(malformed) + + def test_adr_product_requirement_references_exist() -> None: """Accepted ADR traceability cannot point at a removed PRD requirement.""" product_requirements = _PRODUCT_REQUIREMENTS.read_text(encoding="utf-8") From a2295720dcbcf98bfe1ab101910e2afcf31ba942 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 20:08:48 +0900 Subject: [PATCH 6/7] fix(docs): parse ADR reference lists without swallowing prose --- tests/test_documentation_hygiene.py | 92 +++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 18 deletions(-) diff --git a/tests/test_documentation_hygiene.py b/tests/test_documentation_hygiene.py index de2e1e2fc..a82fecb97 100644 --- a/tests/test_documentation_hygiene.py +++ b/tests/test_documentation_hygiene.py @@ -19,11 +19,10 @@ ) _ADR_NAME = re.compile(r"^(?P[0-9]{4})-.+\.md$") _PRD_REQUIREMENT_HEADING = re.compile(r"^### (?PPRD-FR-[0-9A-Z-]+)\b", re.MULTILINE) -_PRD_ADR_CLAUSE = re.compile(r"\bADRs?\s+(?P[^.;)]*)") -_ADR_NUMBER_OR_RANGE = re.compile( - r"(?[0-9]{4})(?![0-9])" - r"(?:\s*[\u2013-]\s*(?P[0-9]{4})(?![0-9]))?" -) +_PRD_ADR_MARKER = re.compile(r"\bADRs?\s+") +_ADR_NUMBER = re.compile(r"(?P[0-9]{4})(?!\w)") +_ADR_RANGE_SEPARATOR = re.compile(r"\s*[\u2013-]\s*") +_ADR_LIST_SEPARATOR = re.compile(r"\s*(?:,\s*(?:and\s+)?|/\s*|and\s+)") _PRIVATE_POST_IDENTIFIER = re.compile( r"(?i)" r"\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b" @@ -50,20 +49,54 @@ def _adr_is_current(content: str) -> bool: return not status.startswith(("retired", "superseded by")) +def _adr_number_at(content: str, cursor: int) -> tuple[str, int] | None: + """Read one standalone four-digit ADR number at an expected list position.""" + match = _ADR_NUMBER.match(content, cursor) + if match is not None: + return match.group("number"), match.end() + malformed = re.match(r"[0-9]\w*", content[cursor:]) + if malformed is not None: + raise ValueError(f"malformed ADR reference: {malformed.group(0)}") + return None + + def _referenced_adr_numbers(content: str) -> set[str]: - """Return complete direct ADR references, rejecting malformed numbers.""" + """Return direct ADR reference lists without treating later prose as citations.""" referenced_numbers: set[str] = set() - for clause in _PRD_ADR_CLAUSE.finditer(content): - references = clause.group("references") - malformed = [token for token in re.findall(r"[0-9]+", references) if len(token) != 4] - if malformed: - raise ValueError(f"malformed ADR reference: {malformed[0]}") - for reference in _ADR_NUMBER_OR_RANGE.finditer(references): - start = int(reference.group("start")) - end = int(reference.group("end") or start) - if start > end: - raise ValueError(f"descending ADR range: {start:04d}-{end:04d}") + for marker in _PRD_ADR_MARKER.finditer(content): + cursor = marker.end() + first = _adr_number_at(content, cursor) + if first is None: + continue + while True: + start_text, cursor = first + start = int(start_text) + end = start + + range_separator = _ADR_RANGE_SEPARATOR.match(content, cursor) + if ( + range_separator is not None + and range_separator.end() < len(content) + and content[range_separator.end()].isdigit() + ): + endpoint = _adr_number_at(content, range_separator.end()) + if endpoint is None: + raise ValueError("malformed ADR range endpoint") + end_text, cursor = endpoint + end = int(end_text) + if start > end: + raise ValueError(f"descending ADR range: {start:04d}-{end:04d}") + referenced_numbers.update(f"{number:04d}" for number in range(start, end + 1)) + + list_separator = _ADR_LIST_SEPARATOR.match(content, cursor) + if list_separator is None or list_separator.end() >= len(content): + break + if not content[list_separator.end()].isdigit(): + break + first = _adr_number_at(content, list_separator.end()) + if first is None: + break return referenced_numbers @@ -154,12 +187,35 @@ def test_product_requirement_adr_references_exist() -> None: def test_product_requirement_adr_references_reject_partial_numbers() -> None: - """A valid sibling reference cannot hide a malformed direct ADR number.""" - for malformed in ("ADR 02490, ADR 0250", "ADR 249, ADR 0250"): + """A valid sibling reference cannot hide a malformed direct ADR token.""" + for malformed in ( + "ADR 02490, ADR 0250", + "ADR 249, ADR 0250", + "ADR 0249a, ADR 0250", + "ADR 0249_foo, ADR 0250", + ): with pytest.raises(ValueError, match="malformed ADR reference"): _referenced_adr_numbers(malformed) +def test_product_requirement_adr_references_stop_before_prose_numbers() -> None: + """Quantities after a complete ADR citation are prose, not malformed references.""" + assert _referenced_adr_numbers("ADR 0249 governs 23 groups") == {"0249"} + assert _referenced_adr_numbers("ADR 0249 supports version 2 for 23 groups") == {"0249"} + + +def test_product_requirement_adr_reference_lists_expand_ranges() -> None: + """Grouped ADR lists retain every standalone number and inclusive range member.""" + assert _referenced_adr_numbers("ADRs 0249, 0250, and 0253\u20130255") == { + "0249", + "0250", + "0253", + "0254", + "0255", + } + assert _referenced_adr_numbers("ADRs 0249/0250") == {"0249", "0250"} + + def test_adr_product_requirement_references_exist() -> None: """Accepted ADR traceability cannot point at a removed PRD requirement.""" product_requirements = _PRODUCT_REQUIREMENTS.read_text(encoding="utf-8") From 889b9b7e71e5e59f068be1b53112e5a859e54427 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 11:39:55 +0900 Subject: [PATCH 7/7] docs: refresh PR #847 exact-head baseline --- docs/product-technical-gap-baseline.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 539817cf9..62651cf02 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -1,5 +1,22 @@ # Product & Technical Gap Baseline +> Exact-head loop overlay: 2026-09-21 KST. Protected `main` is +> `83eba56149eb802cd63642c507c324c9976ec78e` (#931). This candidate is +> PR #847 at `9bb4f07a61a9275953974ef276a4af81940ba883`, based directly on that +> protected head. GitHub search returns at least 100 open PRs (the query page +> limit) and 42 open issues; the PR total is deliberately not promoted to an +> exact count. PR #847 is Draft, mechanically mergeable, has no unresolved +> review thread, no qualifying independent exact-head approval, and no +> transferable successful hosted check on this head. It therefore remains +> candidate documentation evidence, not protected delivery. The candidate +> removes duplicated PRD requirement identifiers, keeps unsupported full SOC, +> O*NET linkage, and review-UI claims explicitly unavailable, and adds a +> fail-closed documentation regression. No ADR status, API, schema, runtime, +> model, or release number changes. Canonical remote names rechecked in this +> cycle are `ContextualWisdomLab/LineageWeave`, +> `ContextualWisdomLab/RankWeave`, `ContextualWisdomLab/ThreadWeave`, +> `ContextualWisdomLab/disksage`, and `ContextualWisdomLab/TEPP`. + > Exact-head loop overlay: 2026-08-31 KST. Protected `main` is > `cb187cadee5fb6c46d8a944815ccc154a1e028d1`, the merge SHA for #782. > GitHub records no independent `APPROVED` review on #782 and its author merged