diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 3488003..f561643 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -16,8 +16,8 @@ | task | CFED-WP-0001-T05 | done | — | workplans/CFED-WP-0001-foundation.md | | task | CFED-WP-0001-T06 | done | — | workplans/CFED-WP-0001-foundation.md | | task | CFED-WP-0001-T07 | done | — | workplans/CFED-WP-0001-foundation.md | -| task | CFED-WP-0001-T08 | todo | — | workplans/CFED-WP-0001-foundation.md | -| task | CFED-WP-0001-T09 | wait | — | workplans/CFED-WP-0001-foundation.md | +| task | CFED-WP-0001-T08 | done | — | workplans/CFED-WP-0001-foundation.md | +| task | CFED-WP-0001-T09 | todo | — | workplans/CFED-WP-0001-foundation.md | | task | CFED-WP-0001-T10 | wait | — | workplans/CFED-WP-0001-foundation.md | | task | CFED-WP-0001-T11 | done | — | workplans/CFED-WP-0001-foundation.md | | task | CFED-WP-0001-T12 | done | — | workplans/CFED-WP-0001-foundation.md | diff --git a/docs/evidence/2026-09-05-counterparty-validate.py b/docs/evidence/2026-09-05-counterparty-validate.py index 6bd5150..9c3052b 100644 --- a/docs/evidence/2026-09-05-counterparty-validate.py +++ b/docs/evidence/2026-09-05-counterparty-validate.py @@ -44,7 +44,10 @@ for row in manifest['imports']: text = historical.decode() if text.startswith('---\n'): upstream_fm = yaml.safe_load(text.split('---')[1]) - upstream_owned = set(upstream_fm['owned_concepts']) + upstream_owned = set(upstream_fm.get('owned_concepts', [])) + # Organization frontmatter records only its incremental social extension. + # Include explicit legacy definitions, never bare imported anchors. + upstream_owned.update(re.findall(r'^(?:An? )?\*\*(.+?)\*\* is\b', text, re.M)) else: upstream_owned = set(re.findall(r'^## \d+\.\d+[a-z]? (.+)$', text, re.M)) assert set(row['concepts']) <= upstream_owned, set(row['concepts']) - upstream_owned diff --git a/docs/evidence/2026-09-06-interface-cards.json b/docs/evidence/2026-09-06-interface-cards.json new file mode 100644 index 0000000..739610b --- /dev/null +++ b/docs/evidence/2026-09-06-interface-cards.json @@ -0,0 +1,15 @@ +{ + "gate": "G7", + "result": "pass", + "cards": 3, + "reciprocal_peer_links": 6, + "import_edges": 6, + "ledger_concepts_and_seed_covered": 59, + "imported_concepts": 25, + "historical_and_current_import_blobs_checked": 8, + "source_commit": "372f671eef861e3f180eb5f9fc2fbd8c479ef8b7", + "upstream_changes_since_review": [], + "navigation_links_checked": 27, + "custodian_domain_concepts": 0, + "family": "seeded-not-authored" +} diff --git a/docs/evidence/2026-09-06-interface-cards.md b/docs/evidence/2026-09-06-interface-cards.md new file mode 100644 index 0000000..e2d9c40 --- /dev/null +++ b/docs/evidence/2026-09-06-interface-cards.md @@ -0,0 +1,100 @@ +# G7 — Reciprocal canon interfaces, 2026-09-06 + +CFED-WP-0001-T08 publishes three repository-owned cards: + +- [InfoTechCanon](../../../info-tech-canon/infospace/interfaces/federation.yaml), implemented by [INFO-WP-0025](../../../info-tech-canon/workplans/INFO-WP-0025-federation-interface.md). +- [CommerceCanon](../../../commerce-canon/infospace/interfaces/federation.yaml), implemented by [COMMERCE-WP-0004](../../../commerce-canon/workplans/COMMERCE-WP-0004-federation-interface.md). +- [Custodian canon](../../../the-custodian/canon/interfaces/federation.yaml), implemented by [THE-WP-0001](../../../the-custodian/workplans/THE-WP-0001-federation-interface.md). + +The cards follow InfoTechCanonCore §24 and its existing JSON Schema. They name +source authority, imports, available exports, source-of-truth boundaries, +limitations, and both peer cards. Card shape uses the schema's string consumer +and array canon_surfaces; richer Core fields are additive. This does not claim +a new standardized runtime exchange protocol or repair every older card template. + +Six reciprocal peer links and six actual import edges are checked: each domain +canon imports accepted CUST-ADR-006 governance; Commerce also imports four +InfoTech models. A reciprocal navigation link does not invent a reverse semantic +import. Custodian exports accepted federation governance, owns no domain +semantics, and leaves the accepted ADR unchanged. + +Exports cover all 58 ledger domain concepts and the separately classified Family +seed. Non-canonical convenience terms remain in model resolution guidance, not +new canonical roots. Community/Household remain the draft social extension; +Family remains seeded-not-authored. No stable model promotion or consumer +adoption is asserted. + +## Explicit upstream review + +Commerce retains its [original import manifest](../../../commerce-canon/infospace/models/counterparty/import-history/2026-09-05-imports.json) +byte-for-byte against published Commerce revision +`bc67e1f25b140ecb60d1439de984f8361acabc32` (SHA-256 +`60a6c5187f1e208dcf763913d4d6a98840bc934d238f25d660c8ff3b5ea241d9`). +The [review record](../../../commerce-canon/infospace/models/counterparty/import-review-2026-09-06.md) +advances the upstream source from `361c944325934ccdb190470baf6f55440b6b486e` to +`372f671eef861e3f180eb5f9fc2fbd8c479ef8b7`. Only the organization source changed: +the social-collective extension and incremental ownership metadata. All 25 +imported concepts remain the same. Current kernel references acknowledge the +completed extension and Family seed without adopting either as a new Counterparty +import. Eight source blobs verify both revisions and their hashes. + +The T06 reproducer now recognizes explicit legacy definitions alongside +incremental frontmatter; bare retained import anchors never establish ownership. +No historical workplan, ADR, original research or frozen assimilation source was +rewritten. + +## Validation + +[Machine-readable result](2026-09-06-interface-cards.json) records G7 pass. +Reproduce from this repository with adjacent canon checkouts: + +```bash +python3 tools/validate_interfaces.py --workspace .. +python3 -m unittest discover -s tools -p 'test*.py' +python3 tools/validate_ownership.py --source-repo ../commerce-canon +python3 tools/validate_corpus.py --commerce-repo ../commerce-canon --info-tech-repo ../info-tech-canon +python3 docs/evidence/2026-09-05-counterparty-validate.py --commerce-repo ../commerce-canon --info-tech-repo ../info-tech-canon +make -C ../info-tech-canon check +``` + +- Three schema-valid cards; six reciprocal peer links; 59 ledger concept/seed + assignments covered; 25 imports from four models; zero source drift. +- Six G7 rejection tests check missing reciprocity, duplicate Evidence ownership, + Family model promotion, Custodian domain claims, invalid schema shape and + undeclared imports. Seven existing ownership tests also pass (13 total). +- InfoTechCanon: 46 tests pass, generated discovery surfaces current, canon + validation and small-saas proof pass. 81 artifacts, 14 models and one Family + seed; the interface adds one discoverable artifact and no model. Existing + exact reciprocal model-import cycle review remains in force. +- Counterparty: 18 owned concepts, 25 imports and 53 protected files unchanged. +- Corpus: 45 unchanged originals, 69 snapshots, 153 exact fragments and 479 links + still verify. Ownership ledger has zero unowned or multiply owned targets. + +G7 is complete after publishing these revisions. CFED-WP-0001-T09 retains the +fleet reference sweep and REUSE-WP-0021 dependency; T10 retains residual review. + +## State Hub synchronization limitation and live handoff + +InfoTech and Commerce native records synchronized successfully. Custodian also +created THE-WP-0001 and its two completed tasks, but its repository-wide checker +reported 13 historical assessment failures: ten missing archived CUST-WP-0054 +task bindings, missing archived CUST-WP-0057/0058 workplan bindings, and one +active retired ad-hoc orphan. Prefix warnings expose the explicit AGENTS file +convention disagreeing with header/database prefixes (THE/CUST and INFO/ITC). + +Live [CUST-IN-0017](../../../the-custodian/intakes.md), origin residual from +THE-WP-0001, owns triage and evidence-backed disposition. Archived records and +accepted ADR were preserved. G7 passes; a clean whole-Custodian State Hub +assessment is not claimed. Decision: `689eeb84-8b46-4dae-be5d-baa8e654f6ee`. + +## Publication + +- InfoTechCanon implementation `28c624b`, synchronized index `9e89a19`. +- CommerceCanon implementation `482fc1d`, repository-manager verified publication + and primary projection `1ac87592904be384dd1230e9b4ad5c9c10cc5da3`. +- Custodian interface and live residual `1df777b`. +- CUST-IN-0017 indexed as `01a073cf-51ca-7fa7-a432-658ba3718382`. + +All native interface workplans are finished and their tasks are done. Project +T08 is done; T09 is ready to execute, with T10 still waiting on final residual +review. Overall foundation progress is 11/13 tasks. diff --git a/ledger/README.md b/ledger/README.md index 2725ae6..32dfd41 100644 --- a/ledger/README.md +++ b/ledger/README.md @@ -71,3 +71,6 @@ The [corpus distribution ledger](corpus-distribution.json) records T07's frozen research destinations and exact shared fragments. Reproduce G6 with `python3 tools/validate_corpus.py --commerce-repo ../commerce-canon --info-tech-repo ../info-tech-canon` from the project root. Historical source assertions are not current definitions. + +[Reciprocal interface evidence](../docs/evidence/2026-09-06-interface-cards.md) +records the three published canon cards and explicit upstream import review (G7). diff --git a/tools/test_interfaces.py b/tools/test_interfaces.py new file mode 100644 index 0000000..93464f5 --- /dev/null +++ b/tools/test_interfaces.py @@ -0,0 +1,60 @@ +"""Regression checks for G7 rejection paths; run beside the three canon checkouts.""" +from pathlib import Path +import unittest +from unittest.mock import patch + +import yaml +import jsonschema + +from validate_interfaces import validate + +WORKSPACE = Path(__file__).resolve().parents[2] +READ_TEXT = Path.read_text + + +class InterfaceGateTests(unittest.TestCase): + def reject_card(self, repo, mutate, message, error=ValueError): + suffix = 'canon/interfaces/federation.yaml' if repo == 'the-custodian' else 'infospace/interfaces/federation.yaml' + target = WORKSPACE / repo / suffix + + def read(path, *args, **kwargs): + content = READ_TEXT(path, *args, **kwargs) + if path == target: + card = yaml.safe_load(content) + mutate(card) + return yaml.safe_dump(card) + return content + + with patch.object(Path, 'read_text', read): + with self.assertRaisesRegex(error, message): + validate(WORKSPACE) + + def test_missing_reciprocal_peer_is_rejected(self): + self.reject_card('info-tech-canon', lambda c: c['peers'].pop(), 'peer coverage') + + def test_duplicate_evidence_owner_is_rejected(self): + self.reject_card('commerce-canon', lambda c: c['exports'][0]['concepts'].append('Evidence'), + 'Duplicate domain owner: Evidence') + + def test_family_model_promotion_is_rejected(self): + def promote(card): + entry = next(e for e in card['exports'] if e['artifact'] == 'family-area') + entry['kind'] = 'domain-model' + self.reject_card('info-tech-canon', promote, 'Family promoted') + + def test_custodian_domain_claim_is_rejected(self): + self.reject_card('the-custodian', lambda c: c['exports'][0]['concepts'].append('Evidence'), + 'Governance claims domain semantics') + + def test_invalid_schema_shape_is_rejected(self): + self.reject_card('info-tech-canon', lambda c: c.update(consumer={'repo': 'info-tech-canon'}), + "is not of type 'string'", jsonschema.ValidationError) + + def test_import_exceeding_export_is_rejected(self): + def extend(card): + card['imports'][1]['concepts'].append('Undeclared concept') + self.reject_card('commerce-canon', extend, 'Import exceeds export') + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/validate_interfaces.py b/tools/validate_interfaces.py new file mode 100644 index 0000000..639224b --- /dev/null +++ b/tools/validate_interfaces.py @@ -0,0 +1,176 @@ +"""Check G7 federation cards, reciprocal contracts, ownership and review provenance. + +Run from a project checkout beside the three canons. Requires PyYAML/jsonschema. +Repository-qualified card paths resolve below --workspace, never below the card. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path +import re +import subprocess + +import jsonschema +import yaml + +CARD_PATHS = { + 'info-tech-canon': 'infospace/interfaces/federation.yaml', + 'commerce-canon': 'infospace/interfaces/federation.yaml', + 'the-custodian': 'canon/interfaces/federation.yaml', +} + + +def require(condition, message): + if not condition: + raise ValueError(message) + + +def sha(data): + return hashlib.sha256(data).hexdigest() + + +def git_blob(repo, commit, path): + require(bool(re.fullmatch(r'[0-9a-f]{40}', commit)), 'Source must use a full Git commit') + return subprocess.check_output(['git', 'show', f'{commit}:{path}'], cwd=repo) + + +def declared_concepts(text): + """Support complete metadata and older explicit prose definitions. + + A section heading alone cannot establish ownership: transferred concepts + deliberately keep their old anchors as imports. + """ + fm = yaml.safe_load(text.split('---', 2)[1]) if text.startswith('---\n') else {} + return set(fm.get('owned_concepts', [])) | set( + re.findall(r'^(?:An? )?\*\*(.+?)\*\* is\b', text, re.M) + ) + + +def validate(workspace): + project = Path(__file__).resolve().parents[1] + schema_path = 'infospace/schemas/interface-card.schema.yaml' + schema = yaml.safe_load((workspace / 'info-tech-canon' / schema_path).read_text()) + jsonschema.Draft202012Validator.check_schema(schema) + cards = {repo: yaml.safe_load((workspace / repo / path).read_text()) + for repo, path in CARD_PATHS.items()} + ledger = json.loads((project / 'ledger/concept-ownership.json').read_text()) + targets = [t for e in ledger['entries'] for t in e['targets'] if t['kind'] in {'concept', 'seed'}] + targets += ledger['additional_required_concepts'] + exports = {} + ownership = {} + import_edges = 0 + peer_links = 0 + for repo, card in cards.items(): + jsonschema.validate(card, schema) + require(card['consumer'] == repo, f'{repo}: wrong consumer') + require(card['status'] == 'published', f'{repo}: unpublished') + require(card['schema_source'] == {'repository': 'info-tech-canon', 'path': schema_path}, 'Schema drift') + peers = card['peers'] + require(len(peers) == 2 and {p['repository'] for p in peers} == set(cards) - {repo}, f'{repo}: peer coverage') + for peer in peers: + other = peer['repository'] + require(peer['card'] == CARD_PATHS[other], f'{repo}: bad peer card path') + require(any(p['repository'] == repo and p['card'] == CARD_PATHS[repo] + for p in cards[other]['peers']), f'{repo}: nonreciprocal peer') + peer_links += 1 + require(card['canon_surfaces'] == [e['artifact'] for e in card['exports']], 'Export index drift') + for entry in card['exports']: + key = (repo, entry['artifact']) + require(key not in exports, f'Duplicate export {key}') + exports[key] = entry + require((workspace / repo / entry['path']).is_file(), f'Missing export {key}') + if entry['kind'] == 'ecosystem-governance': + require(repo == 'the-custodian' and entry['concepts'] == [], 'Governance claims domain semantics') + require(set(entry['governs']) == set(cards) - {repo}, 'Governance coverage') + else: + registry = yaml.safe_load((workspace / repo / 'canon.yaml').read_text()) + matches = [m for m in registry['models'] + registry.get('concept_areas', []) if m['id'] == entry['artifact']] + require(len(matches) == 1, f'Unregistered export {key}') + for field in ['status', 'path']: + require(entry[field] == matches[0][field], f'{key}: registry {field} drift') + if entry['artifact'] == 'family-area': + require(entry['kind'] == 'concept-area-seed' and entry['availability'] == 'seeded-not-authored', 'Family promoted') + for concept in entry['concepts']: + require(concept not in ownership, f'Duplicate domain owner: {concept}') + ownership[concept] = (repo, entry['ledger_model']) + authority = card['authority'] + require(authority == cards['the-custodian']['authority'], 'Authority mismatch') + require(authority['repository'] == 'the-custodian' and authority['artifact'] == 'CUST-ADR-006' + and authority['revision'] == 'accepted-1', 'Unreviewed authority') + adr = git_blob(workspace / 'the-custodian', authority['source_commit'], authority['path']) + require(sha(adr) == authority['sha256'], 'Pinned ADR hash mismatch') + require(adr == (workspace / 'the-custodian' / authority['path']).read_bytes(), 'ADR changed since review') + for target in targets: + require(ownership.get(target['concept']) == (target['owner']['canon'], target['owner']['model']), + f"Ledger disagreement: {target['concept']}") + for repo, card in cards.items(): + seen = set() + for entry in card['imports']: + key = (entry['repository'], entry['artifact']) + require(key not in seen, f'{repo}: duplicate import') + seen.add(key) + require(key in exports and entry['repository'] != repo, f'{repo}: missing upstream export {key}') + require(entry['kind'] == exports[key]['kind'], 'Import kind mismatch') + require(set(entry['concepts']) <= set(exports[key]['concepts']), 'Import exceeds export') + require(not any(ownership[c][0] == repo for c in entry['concepts']), 'Import redefined locally') + import_edges += 1 + require(cards['the-custodian']['imports'] == [], 'Custodian domain dependency introduced') + require(len(cards['info-tech-canon']['imports']) == 1, 'Unreviewed reverse domain import') + require(len(cards['the-custodian']['exports']) == 1, 'Custodian federation scope drift') + commerce = workspace / 'commerce-canon' + model_dir = commerce / 'infospace/models/counterparty' + manifest_path = model_dir / 'imports.json' + current = json.loads(manifest_path.read_text()) + review = current['review'] + require((model_dir / review['record']).is_file(), 'Missing import review') + previous_bytes = (model_dir / review['previous_manifest']).read_bytes() + require(sha(previous_bytes) == review['previous_manifest_sha256'], 'Historical manifest changed') + previous = json.loads(previous_bytes) + require(previous['source_commit'] == '361c944325934ccdb190470baf6f55440b6b486e', 'Original review pin changed') + require(review['concept_changes'] == [], 'Unexpected concept adoption') + require([(r['model'], r['concepts']) for r in previous['imports']] == + [(r['model'], r['concepts']) for r in current['imports']], 'Unreviewed import change') + checked_blobs = 0 + drift = [] + for manifest in [previous, current]: + require(manifest['source_repository'] == 'info-tech-canon', 'Wrong import source') + for entry in manifest['imports']: + blob = git_blob(workspace / 'info-tech-canon', manifest['source_commit'], entry['path']) + require(sha(blob) == entry['sha256'], f"Pinned import hash mismatch: {entry['model']}") + require(set(entry['concepts']) <= declared_concepts(blob.decode()), f"Import not owned: {entry['model']}") + checked_blobs += 1 + if manifest is current and blob != (workspace / 'info-tech-canon' / entry['path']).read_bytes(): + drift.append(entry['path']) + domain_imports = [e for e in cards['commerce-canon']['imports'] if e['kind'] == 'domain-model'] + require(len(domain_imports) == len(current['imports']) == 4, 'Import model coverage') + for row in current['imports']: + exported = exports[('info-tech-canon', row['model'])] + require(exported['path'] == row['path'], 'Import/export source mismatch') + matches = [e for e in domain_imports if e['artifact'] == row['model']] + require(len(matches) == 1 and matches[0]['concepts'] == row['concepts'], 'Card/manifest mismatch') + require(commerce / matches[0]['manifest'] == manifest_path, 'Wrong manifest pointer') + links = 0 + for repo, path in CARD_PATHS.items(): + root = workspace / repo + for document in [root / 'README.md', root / Path(path).parent / 'README.md']: + for link in re.findall(r'\]\(([^)]+)\)', document.read_text()): + if '://' in link or link.startswith('#'): + continue + require((document.parent / link.split('#')[0]).exists(), f'Broken link: {document}: {link}') + links += 1 + return {'gate': 'G7', 'result': 'pass', 'cards': len(cards), 'reciprocal_peer_links': peer_links, + 'import_edges': import_edges, 'ledger_concepts_and_seed_covered': len(targets), + 'imported_concepts': sum(len(e['concepts']) for e in domain_imports), + 'historical_and_current_import_blobs_checked': checked_blobs, + 'source_commit': current['source_commit'], 'upstream_changes_since_review': drift, + 'navigation_links_checked': links, 'custodian_domain_concepts': 0, + 'family': 'seeded-not-authored'} + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--workspace', type=Path, default=Path(__file__).resolve().parents[2]) + args = parser.parse_args() + print(json.dumps(validate(args.workspace.resolve()), indent=2)) diff --git a/workplans/CFED-WP-0001-foundation.md b/workplans/CFED-WP-0001-foundation.md index e5496d0..825911d 100644 --- a/workplans/CFED-WP-0001-foundation.md +++ b/workplans/CFED-WP-0001-foundation.md @@ -299,7 +299,7 @@ does not promote historical assertions. G6 passes; T08 is now todo. ```task id: CFED-WP-0001-T08 -status: todo +status: done priority: medium state_hub_task_id: "e0558c32-415f-5563-bca2-438b35fa6b53" ``` @@ -317,11 +317,22 @@ kernel references that still mark upstream extensions pending. Keep historical import revisions verifiable; do not silently adopt new upstream definitions. +**Result (2026-09-06):** Native INFO-WP-0025, COMMERCE-WP-0004 and THE-WP-0001 +publish reciprocal interface cards in all three canons. [G7 evidence](../docs/evidence/2026-09-06-interface-cards.md) +verifies three schema-valid cards, six reciprocal links, all ledger concept/seed +assignments and 25 unchanged imports. Commerce advances its reviewed upstream +pin with the previous manifest preserved; kernel references acknowledge T12/T13. +InfoTechCanon checks pass (46 tests); project rejection/ownership tests pass (13). +Custodian's historical projection failures and conflicting generated prefix +instructions have live residual [CUST-IN-0017](../../the-custodian/intakes.md). +G7 passes; T09 is now todo. Whole-fleet consistency is not claimed by this gate. + + ## Sweep fleet references ```task id: CFED-WP-0001-T09 -status: wait +status: todo priority: medium state_hub_task_id: "42ebf476-6a45-5e23-b507-eb90bc4f5c07" ```