From bd85fcd179a937f2569a0100b65e70732abbe721 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 4 Oct 2026 09:02:35 +0200 Subject: [PATCH] Add verified reference lookup and immutable consumer receipts Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a1045b-933d-7aa1-b2db-104557487a79 --- Makefile | 4 + SCOPE.md | 4 +- START.md | 17 ++ WORK-RECORDS.md | 5 + .../2026-10-04-reference-consumption.json | 52 ++++++ feedback/ConsumerIntegration.md | 43 +++++ feedback/README.md | 4 + feedback/decision-input.example.json | 28 +++ .../2026-10-04T084941+0200-scope-vs-intent.md | 22 ++- ...lity.infotech.frontend-pattern-language.md | 12 +- scripts/reference_consume.py | 176 ++++++++++++++++++ templates/PatternUseReceipt.yaml | 1 + tests/test_reference_consume.py | 176 ++++++++++++++++++ .../FEP-WP-0008-use-driven-improvement.md | 15 ++ .../FEP-WP-0010-reference-consumption.md | 35 +++- 15 files changed, 583 insertions(+), 11 deletions(-) create mode 100644 docs/evidence/2026-10-04-reference-consumption.json create mode 100644 feedback/decision-input.example.json create mode 100644 scripts/reference_consume.py create mode 100644 tests/test_reference_consume.py diff --git a/Makefile b/Makefile index 14abca8..88f704c 100644 --- a/Makefile +++ b/Makefile @@ -36,3 +36,7 @@ reference-check: reference-test: PYTHONDONTWRITEBYTECODE=1 "$(BENCH_PY)" -m pytest -q -p no:cacheprovider tests/test_reference_preview.py + +.PHONY: consumption-test +consumption-test: + PYTHONDONTWRITEBYTECODE=1 "$(BENCH_PY)" -m pytest -q -p no:cacheprovider tests/test_reference_consume.py diff --git a/SCOPE.md b/SCOPE.md index 5f25789..2537807 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -1,6 +1,6 @@ # SCOPE -Reviewed: 2026-10-04 · Capability audit at `07f1f37`. +Reviewed: 2026-10-04 · Audited at `07f1f37`; updated with WP-0010 delivery. ## What this repository delivers @@ -20,6 +20,8 @@ Research fixtures exercise bounded mechanisms; they are not implementation recip | --- | --- | --- | | Human entry and two core sequences | Follow shared-record editing or offline-work decisions, alternatives, failure traces and implementation questions | [START.md](START.md), [transactional](sequences/TransactionalApplication.md), [offline](sequences/LocalFirstApplication.md); synthetic VC-01/02 guidance | | Frozen 0.1 candidate reference | Read 17 pinned draft hypotheses (8 core, 9 extensions), 9 conditional proposed relations, classifications and evidence gaps | [Release registry](reference/releases.json), [index](reference/0.1/index.json); not an admitted canonical graph | +| Local reference consumption | Filter the verified preview by explicit candidate ID, improving forces and responsibilities; emit corpus/packaging citations, force costs and conditional relations; clarify or return no match | [reference_consume.py](scripts/reference_consume.py); deterministic metadata filtering, not natural-language retrieval, applicability or compatibility proof | +| Consumer event preparation | Produce a schema-v1 event with verified release pins, explicit provenance, unknown or evidenced producer outcomes, immutable file and exact collector-index digest | [Consumer instructions](feedback/ConsumerIntegration.md#local-lookup-and-closeout-helper); does not install hooks, enroll sources or independently verify outcome/lineage | | Current research corpus | Inspect 18 developed candidate hypotheses with evidence profiles and retained source/runtime reviews | [Evolution report](research/evolution/EvolutionReport.md); the additional live candidate is outside frozen 0.1, no canonical admissions | | Implementation mappings | Locate scoped mechanisms for transactional and offline recovery | [TransactionalRecovery](technologies/TransactionalRecovery.md), [OfflineRecovery](technologies/OfflineRecovery.md); no product supplies a certified complete configuration | | Metadata and publication checks | Validate contract examples, candidate inventory/hashes, classification, evidence locators and immutable release artifacts | `make schema-test evolution-check classification-check reference-check`; checks establish structural traceability, not source truth | diff --git a/START.md b/START.md index 9f19cf5..95e9697 100644 --- a/START.md +++ b/START.md @@ -60,6 +60,23 @@ intended to keep learning. ## Agent reference +For an explicit candidate, force or responsibility, use the local pinned-reference +helper: + +```bash +python3 scripts/reference_consume.py --version 0.1 lookup --candidate CAND-0007 +python3 scripts/reference_consume.py --version 0.1 lookup --force recoverability --responsibility mutation +``` + +Filters intersect; multiple candidate IDs form an allowlist. Results carry force +costs, evidence gaps, conditional relations and commit-specific contract/evidence +locators. A metadata match is a research lead, not an applicability or compatible +configuration proof. With no filters the helper requests context; valid filters with +no results produce `no-match`. Unknown vocabulary or unpublished IDs fail explicitly. +Read the complete pinned contracts before making the consumer's decision. +The [consumer receipt instructions](feedback/ConsumerIntegration.md#local-lookup-and-closeout-helper) +explain how to retain that decision and its unknown or later observed outcome. + Read [index.json](reference/0.1/index.json) for stable research identities, proposed relations, classification, evidence and gaps. It is a candidate reference export, not an admitted canonical graph. Read the linked contracts before making a recommendation; diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index b2b6cce..babc9c2 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -17,6 +17,7 @@ | workplan | FEP-WP-0007 | finished | — | workplans/FEP-WP-0007-language-v01-preview.md | | workplan | FEP-WP-0008 | blocked | — | workplans/FEP-WP-0008-use-driven-improvement.md | | workplan | FEP-WP-0009 | backlog | — | workplans/FEP-WP-0009-m1-independent-validation.md | +| workplan | FEP-WP-0010 | finished | — | workplans/FEP-WP-0010-reference-consumption.md | | task | FEP-WP-0001-T01 | done | — | workplans/FEP-WP-0001-statehub-bootstrap.md | | task | FEP-WP-0001-T02 | done | — | workplans/FEP-WP-0001-statehub-bootstrap.md | | task | FEP-WP-0001-T03 | done | — | workplans/FEP-WP-0001-statehub-bootstrap.md | @@ -46,3 +47,7 @@ | task | FEP-WP-0009-T01 | wait | — | workplans/FEP-WP-0009-m1-independent-validation.md | | task | FEP-WP-0009-T02 | todo | — | workplans/FEP-WP-0009-m1-independent-validation.md | | task | FEP-WP-0009-T03 | wait | — | workplans/FEP-WP-0009-m1-independent-validation.md | +| task | FEP-WP-0010-T01 | done | — | workplans/FEP-WP-0010-reference-consumption.md | +| task | FEP-WP-0010-T02 | done | — | workplans/FEP-WP-0010-reference-consumption.md | +| task | FEP-WP-0010-T03 | done | — | workplans/FEP-WP-0010-reference-consumption.md | +| task | FEP-WP-0010-T04 | done | — | workplans/FEP-WP-0010-reference-consumption.md | diff --git a/docs/evidence/2026-10-04-reference-consumption.json b/docs/evidence/2026-10-04-reference-consumption.json new file mode 100644 index 0000000..be482c7 --- /dev/null +++ b/docs/evidence/2026-10-04-reference-consumption.json @@ -0,0 +1,52 @@ +{ + "observed_at": "2026-10-04T07:00:38.801351+00:00", + "workplan": "FEP-WP-0010", + "kind": "disclosed-development-verification", + "assessment": "history/2026-10-04T084941+0200-scope-vs-intent.md", + "audit_revision": "07f1f373744986a22bc4f18710de94534f40343d", + "reference": { + "schema_version": 1, + "current": "0.1", + "releases": { + "0.1": { + "packaging_revision": "53bb65e7a6eb1a2fa933e5b41dfa5a0e7409d14d", + "manifest_sha256": "d7f60ac04817820b805265d1d747cfbb8d2269b7bd6244cd9d3e8d2fd44ef0b8" + } + } + }, + "tests": { + "consumption": 31, + "schema": 2, + "bench_and_admission": 33, + "evolution": 22, + "reference_preview": 16, + "total": 104, + "skipped": 0 + }, + "checks": [ + "synthetic bench plan; no provider execution", + "evolution consistency", + "classification consistency", + "frozen reference consistency", + "actual activity-core validator accepted generated synthetic event", + "reuse-surface capability schema and relations", + "changed Markdown local links", + "git diff --check" + ], + "owner_contract": { + "repo": "activity-core", + "revision": "3dd68d118b3fb60e45926dcb238d4f1ab4d447ac", + "path": "src/activity_core/context_resolvers/frontend_patterns.py", + "sha256": "33aae2f3f0a17dfa90580c611de474064ca52b0aebf73939a9476a8560f46757" + }, + "new_real_uses": 0, + "new_consumer_enrollments": 0, + "model_calls": 0, + "production_mutations": 0, + "limitations": [ + "Metadata filtering does not establish applicability or compatibility.", + "Synthetic tests do not establish real value, independent human validation or held-out retrieval.", + "Producer preserves supplied lineage/outcome locators; collector and empirical review retain independent verification.", + "Hosted registration, actual closeout integration and linked consumer outcomes remain WP-0008-T01/T02." + ] +} diff --git a/feedback/ConsumerIntegration.md b/feedback/ConsumerIntegration.md index d21ee67..5d73032 100644 --- a/feedback/ConsumerIntegration.md +++ b/feedback/ConsumerIntegration.md @@ -12,6 +12,49 @@ implementation authority. A deployment repair or documentation-only inspection o this pattern repository is not an external frontend use. Unsupported contexts should produce an explicit no-match decision, not a forced pattern selection. +## Local lookup and closeout helper + +The local helper is available before hosted federation enrollment. From the reference +checkout, `python3 scripts/reference_consume.py --version 0.1 lookup --candidate CAND-0007` +returns a verified candidate with exact corpus/packaging citation pins. Force and +responsibility filters narrow research leads; they do not make the consumer decision. +Read the linked contracts and their alternatives. Preserve abstention and unsupported +needs; do not turn a matching force into a complete architecture claim. + +The [decision-input example](decision-input.example.json) is explicitly synthetic, +with dummy provenance. It is a format example, never an eligible real use. For an +actual eligible task, the consumer creates its own JSON with stable use/event IDs, +explicit `kind`, full consumer commit, task/producer/run references, constraints, +considered/selected IDs, action/reason/artifact reference and an observable outcome +expectation. Unknown is the default outcome; reported helpfulness requires observation +and evidence locators and still awaits independent verification. No private code, +credentials or transcript belongs in the input. + +From that consumer's checkout, using the path to the reference tool: + +```bash +python3 /path/to/frontend-patterns/scripts/reference_consume.py --version 0.1 receipt \ + --input decision.json --output research/pattern-use/use-01/event-01.json +``` + +The helper validates input and the frozen release, fills its version/packaging/digest +pins, exclusively writes a new source-relative file, and prints the exact path/SHA-256 +entry for the consumer's schema-v1 receipt index. JSON is valid YAML for activity-core's +existing collector. Without `--output`, it prints the event for inspection. It never +overwrites a receipt, enrolls a source or supplies collector-verified origin. + +A later outcome uses the same `use_id`, a new `event_id` and the preceding file's +`previous_event_sha256`. Retain the earlier event/index entry; chronology and predecessor +integrity are checked by the existing collector. The helper preserves a supplied +digest but does not independently verify the event chain or outcome artifact. + +Consumer owners still integrate invocation into their normal closeout hook, commit the +receipt/index and arrange authorized enrollment through WP-0008. This preparation does +not establish hosted discovery, automatic hook installation, real value or the loop's +delayed-artifact refresh. `make consumption-test` includes disclosed synthetic cases +and an integration check against the sibling activity-core validator when its Python +environment is present; release acceptance requires that check to run without a skip. + ## Short planning and closeout instruction > For eligible frontend architecture work, query diff --git a/feedback/README.md b/feedback/README.md index 8ab55c3..2bef5cf 100644 --- a/feedback/README.md +++ b/feedback/README.md @@ -18,6 +18,10 @@ Consumer index (at most 100 entries across one collection): ``` Events follow [PatternUseReceipt.yaml](../templates/PatternUseReceipt.yaml). +The [local producer helper](ConsumerIntegration.md#local-lookup-and-closeout-helper) +prepares verified reference pins and a new immutable JSON/YAML event plus its index +entry from explicit consumer provenance. It does not install the closeout hook or +enroll a consumer. `decision-input.example.json` is a synthetic format fixture. One stable `use_id` links immutable events; `previous_event_sha256` identifies a follow-up. Identical events deduplicate; conflicting IDs, missing predecessors, branches/cycles and malformed records are quarantined in the report. Synthetic diff --git a/feedback/decision-input.example.json b/feedback/decision-input.example.json new file mode 100644 index 0000000..e2fc6e9 --- /dev/null +++ b/feedback/decision-input.example.json @@ -0,0 +1,28 @@ +{ + "use_id": "fixture-consumer:FEP-DEV-01:decision-01", + "event_id": "fixture-decision-01", + "recorded_at": "2026-10-04T06:49:41Z", + "kind": "synthetic", + "consumer": { + "repo": "fixture-consumer", + "revision": "0000000000000000000000000000000000000000", + "task_ref": "fixture:FEP-DEV-01", + "producer": "disclosed-development-fixture", + "run_ref": "fixture:no-external-use" + }, + "context": { + "problem": "A shared-record edit timed out; decide whether creating another attempt is justified.", + "constraints": ["The original operation result is unknown.", "Replay identity and retention are not yet verified."], + "considered": ["CAND-0007"], + "selected": [] + }, + "decision": { + "action": "rejected", + "reason": "Do not claim safe replay before verifying the authority's lookup and retention contract.", + "artifact_ref": null + }, + "outcome": { + "expectation": "A later authority-contract check establishes whether bounded replay is available.", + "next_observation": "Actual consumer would link its authority-contract check; fixture remains unknown." + } +} diff --git a/history/2026-10-04T084941+0200-scope-vs-intent.md b/history/2026-10-04T084941+0200-scope-vs-intent.md index d61f062..73bc071 100644 --- a/history/2026-10-04T084941+0200-scope-vs-intent.md +++ b/history/2026-10-04T084941+0200-scope-vs-intent.md @@ -84,5 +84,23 @@ validation and production autonomy remain separate evidence requirements. ## Verification and completion record -To be filled from implementation receipts before closeout. The capability inventory -must describe the delivered tool behavior and retain all empirical limitations. +FEP-WP-0010 was registered (UUID `c46b02ca-26a8-5313-b0f2-eb0385ff820c`) and its +finite local bridge implemented. The lookup returns explicit matches, needs-context or +no-match with verified pins, full force costs and conditional relations. The producer +validates explicit provenance/selection/outcome input, defaults to unknown, writes +exclusively and emits exact index bytes/digests. Caller classifications and assertions +remain caller claims; no consumer was enrolled and no real use was fabricated. + +104 tests passed with zero skips, including actual activity-core validator acceptance; +synthetic planning, evolution/classification/frozen-publication checks passed. +[Verification record](../docs/evidence/2026-10-04-reference-consumption.json) pins the +owner contract revision/hash. No paid inference or production mutation occurred. +SCOPE, START, consumer instructions and capability guidance were reconciled to delivered +behavior. Existing WP-0008 T01/T02 received the integration/outcome residual in their +live task descriptions before this finite workplan was finished. + +The highest-priority remaining gap is still real consumer decisions and inspectable +outcomes; the next is independent validation. The bridge reduces preparation friction, +but its tests do not establish either outcome. Broader applicability, autonomous +improvement and comparable learning retain the owners and gates listed above. +The resolved implementation decision is `6914093c-e540-4d31-a1ea-607021c5e185`. diff --git a/registry/capabilities/capability.infotech.frontend-pattern-language.md b/registry/capabilities/capability.infotech.frontend-pattern-language.md index 78aa73c..4463c15 100644 --- a/registry/capabilities/capability.infotech.frontend-pattern-language.md +++ b/registry/capabilities/capability.infotech.frontend-pattern-language.md @@ -49,14 +49,14 @@ relations: related_to: [] evidence: documentation: [START.md, reference/0.1/ReleaseReview.md, docs/ReuseAndImprovementLoop.md] - tests: [tests/test_reference_preview.py] + tests: [tests/test_reference_preview.py, tests/test_reference_consume.py] consumer_feedback: [] bug_reports: [] incidents: [] consumer_guidance: recommended_for: [frontend architectural decision notes, reviewing shared-edit and offline-recovery assumptions] not_recommended_for: [claiming canonical maturity or production guarantees, treating a match score as applicability proof] - known_limitations: [all candidates are hypotheses, no independent human usefulness result, use feedback automation not deployed] + known_limitations: [all candidates are hypotheses, no independent human usefulness result, no enrolled consumers, admitted improvement and automatic publication remain open] promotion_history: [] --- @@ -72,5 +72,9 @@ boundaries. Their presence does not imply a complete sequence for those contexts The [reuse and improvement contract](../../docs/ReuseAndImprovementLoop.md) defines how consumer decisions and later outcomes will improve this language. Receipt collection -and unattended improvement require WP-0008 implementation; do not imply that recording -`reused` in registry telemetry already measures helpfulness. +and deterministic reporting are operational through activity-core; consumer closeout +integration, actual outcomes and admitted improvement remain WP-0008 acceptance work. +The [local lookup/receipt helper](../../feedback/ConsumerIntegration.md#local-lookup-and-closeout-helper) +provides verified pins and immutable producer files; it does not establish hosted +registration, consumer value or automatic improvement. Recording `reused` in registry +telemetry does not measure helpfulness. diff --git a/scripts/reference_consume.py b/scripts/reference_consume.py new file mode 100644 index 0000000..3256029 --- /dev/null +++ b/scripts/reference_consume.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 +"""Filter a verified preview and prepare pinned consumer events; no inference.""" +import argparse +from datetime import datetime, timezone +import hashlib +import json +import math +from pathlib import Path +import re + +from reference_preview import ROOT, build, git_blob, release, require +from classification_coverage import section_values, RESPONSIBILITIES + +HOST = 'https://forgejo.coulomb.social/coulomb/frontend-patterns' +SHA = re.compile(r'[0-9a-f]{40}') +DIGEST = re.compile(r'[0-9a-f]{64}') +IDENTITY = re.compile(r'[A-Za-z0-9][A-Za-z0-9:_.-]{0,159}') + + +def published(root=ROOT, version=None): + base, pin, manifest = release(root, version) + generated = build(root, version) + for name, content in generated.items(): + require((root / base / name).read_text() == content, 'Published export changed: ' + name) + return json.loads(generated['index.json']), pin, manifest + + +def locator(path, revision): + return {'path': path, 'revision': revision, + 'url': f'{HOST}/src/commit/{revision}/{path}'} + + +def lookup(index, pin, manifest, candidates=(), forces=(), responsibilities=(), root=ROOT): + known = {c['id'] for c in index['candidates']} + vocab = git_blob(root, manifest['corpus_revision'], 'canon/vocabulary.yaml').decode() + require(set(candidates) <= known, 'Unknown candidate in selected release') + require(set(forces) <= set(section_values(vocab, 'forces')), 'Unknown force') + require(set(responsibilities) <= set(RESPONSIBILITIES), 'Unknown responsibility') + matches = [] + if candidates or forces or responsibilities: + for candidate in index['candidates']: + classification = candidate['classification'] + if candidates and candidate['id'] not in candidates: + continue + if not set(forces) <= set(classification['forces']['improves']): + continue + if not set(responsibilities) <= set(classification['responsibilities']): + continue + record = dict(candidate) + record['contract'] = locator(candidate['path'], index['corpus_revision']) + record['evidence'] = [dict(e, locator=locator(e['ref'], pin['packaging_revision'])) + for e in candidate['evidence']] + matches.append(record) + ids = {c['id'] for c in matches} + return { + 'schema_version': 1, + 'status': 'needs-context' if not (candidates or forces or responsibilities) else ('matches' if matches else 'no-match'), + 'reference': {'version': index['version'], 'packaging_revision': pin['packaging_revision'], + 'manifest_sha256': pin['manifest_sha256'], 'corpus_revision': index['corpus_revision']}, + 'filters': {'candidates': list(candidates), 'improving_forces': list(forces), 'responsibilities': list(responsibilities)}, + 'candidates': matches, + 'relations': [e for e in index['relations'] if e['source'] in ids or e['target'] in ids], + 'entrypoints': {k: locator(v, pin['packaging_revision']) for k, v in index['entrypoints'].items()}, + 'limits': ['Metadata filtering is not applicability, ranking or compatible-configuration proof.', + 'Read full pinned contracts, force costs, alternatives and gaps before selecting.', + 'Relations are conditional proposals; no inverse, transitive or automatic selection.', + 'No match means no record satisfies these filters, not that no architecture exists.', + 'This preview contains hypotheses; independent usefulness remains unverified.'], + } + + +def text_ok(value, limit=1000): + return isinstance(value, str) and 0 < len(value.strip()) <= limit + + +def receipt(source, index, pin, now=None): + require(isinstance(source, dict), 'Decision input must be an object') + fields = {'use_id', 'event_id', 'recorded_at', 'kind', 'consumer', 'context', 'decision', 'outcome'} + require(fields <= source.keys() <= fields | {'previous_event_sha256'}, 'Decision input fields mismatch') + event = json.loads(json.dumps(source)) + event.update(schema_version=1, reference={ + 'version': index['version'], 'packaging_revision': pin['packaging_revision'], + 'manifest_sha256': pin['manifest_sha256']}) + for key in ('use_id', 'event_id'): + require(isinstance(event[key], str) and IDENTITY.fullmatch(event[key]), 'Invalid event identity') + require(event['kind'] in {'real', 'synthetic'}, 'Explicit real/synthetic kind required') + previous = event.setdefault('previous_event_sha256', None) + require(previous is None or isinstance(previous, str) and DIGEST.fullmatch(previous), 'Invalid predecessor digest') + require(isinstance(event['recorded_at'], str), 'Timestamp required') + stamp = datetime.fromisoformat(event['recorded_at'].replace('Z', '+00:00')) + require(stamp.tzinfo is not None and stamp <= (now or datetime.now(timezone.utc)), 'Invalid/future timestamp') + consumer = event['consumer'] + require(isinstance(consumer, dict) and set(consumer) == {'repo', 'revision', 'task_ref', 'producer', 'run_ref'}, 'Consumer fields mismatch') + require(isinstance(consumer['repo'], str) and re.fullmatch(r'[a-z0-9][a-z0-9-]{0,79}', consumer['repo']), 'Invalid consumer repo') + require(isinstance(consumer['revision'], str) and SHA.fullmatch(consumer['revision']), 'Full consumer revision required') + require(all(text_ok(consumer[k]) for k in ('task_ref', 'producer', 'run_ref')), 'Missing consumer provenance') + context = event['context'] + require(isinstance(context, dict) and set(context) == {'problem', 'constraints', 'considered', 'selected'}, 'Context fields mismatch') + require(text_ok(context['problem']), 'Problem required') + require(isinstance(context['constraints'], list) and all(text_ok(x, 500) for x in context['constraints']), 'Invalid constraints') + known = {c['id'] for c in index['candidates']} + for key in ('considered', 'selected'): + values = context[key] + require(isinstance(values, list) and all(isinstance(v, str) for v in values), 'Invalid candidate list') + require(len(values) == len(set(values)) and set(values) <= known, 'Unknown/duplicate candidate in release') + require(set(context['selected']) <= set(context['considered']), 'Unconsidered selection') + decision = event['decision'] + require(isinstance(decision, dict) and set(decision) == {'action', 'reason', 'artifact_ref'}, 'Decision fields mismatch') + require(decision['action'] in {'applied', 'adapted', 'rejected', 'no-match'}, 'Invalid decision action') + require(bool(context['selected']) == (decision['action'] in {'applied', 'adapted'}), 'Action/selection mismatch') + require(text_ok(decision['reason']), 'Decision reason required') + require(decision['artifact_ref'] is None or text_ok(decision['artifact_ref']), 'Invalid artifact reference') + outcome = event['outcome'] + defaults = {'value': 'unknown', 'basis': 'consumer-report', 'stage': 'decision', + 'observation': None, 'evidence_refs': [], 'next_observation': None, + 'measured_decision_minutes': None, 'measured_rework_minutes': None, 'comparison_ref': None} + require(isinstance(outcome, dict) and 'expectation' in outcome and set(outcome) <= set(defaults) | {'expectation'}, 'Outcome fields mismatch') + event['outcome'] = outcome = dict(defaults, **outcome) + require(text_ok(outcome['expectation']), 'Observable expectation required') + require(outcome['value'] in {'helpful', 'harmful', 'mixed', 'no-effect', 'unknown'}, 'Invalid outcome') + require(outcome['basis'] in {'consumer-report', 'model-assessment', 'observed-result'}, 'Invalid outcome basis') + require(outcome['stage'] in {'decision', 'implementation', 'test', 'operation'}, 'Invalid outcome stage') + refs = outcome['evidence_refs'] + require(isinstance(refs, list) and len(refs) <= 10 and all(text_ok(v) for v in refs), 'Invalid outcome evidence') + require(outcome['observation'] is None or text_ok(outcome['observation']), 'Invalid observation') + require(outcome['value'] == 'unknown' or refs and text_ok(outcome['observation']), 'Observed outcome needs evidence') + for key in ('next_observation', 'comparison_ref'): + require(outcome[key] is None or text_ok(outcome[key]), 'Invalid outcome reference') + for key in ('measured_decision_minutes', 'measured_rework_minutes'): + value = outcome[key] + require(value is None or type(value) in (int, float) and math.isfinite(value) and value >= 0, 'Invalid measured effort') + return event + + +def write_receipt(event, relative, consumer_root=None): + require(isinstance(relative, str) and 0 < len(relative) <= 240 + and re.fullmatch(r'[A-Za-z0-9_./-]+', relative) + and all(part not in {'', '.', '..'} for part in relative.split('/')), 'Unsafe receipt path') + root = (consumer_root or Path.cwd()).resolve() + path = root / relative + require(path.resolve().is_relative_to(root), 'Receipt escapes consumer root') + content = (json.dumps(event, indent=2, allow_nan=False) + '\n').encode() + require(len(content) <= 131072, 'Receipt exceeds collector size limit') + path.parent.mkdir(parents=True, exist_ok=True) + with path.open('xb') as stream: + stream.write(content) + return {'path': relative, 'sha256': hashlib.sha256(content).hexdigest()} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--version', help='Published version; defaults to current pointer') + commands = parser.add_subparsers(dest='command', required=True) + query = commands.add_parser('lookup', help='Intersect force/responsibility filters; candidate IDs form an explicit allowlist') + query.add_argument('--candidate', action='append', default=[]) + query.add_argument('--force', action='append', default=[]) + query.add_argument('--responsibility', action='append', default=[]) + prepare = commands.add_parser('receipt', help='Prepare a consumer-authored event with verified release pins') + prepare.add_argument('--input', required=True, type=Path, help='Decision/provenance JSON; see feedback/decision-input.example.json') + prepare.add_argument('--output', help='New source-relative receipt path under current consumer directory; never overwrite') + args = parser.parse_args() + try: + index, pin, manifest = published(version=args.version) + if args.command == 'lookup': + result = lookup(index, pin, manifest, args.candidate, args.force, args.responsibility) + else: + require(args.input.stat().st_size <= 131072, 'Decision input too large') + event = receipt(json.loads(args.input.read_text()), index, pin) + result = write_receipt(event, args.output) if args.output else event + print(json.dumps(result, indent=2, allow_nan=False)) + except (ValueError, TypeError, KeyError, OSError) as exc: + parser.exit(1, f'Reference consumption failed: {exc}\n') + + +if __name__ == '__main__': + main() diff --git a/templates/PatternUseReceipt.yaml b/templates/PatternUseReceipt.yaml index 3839090..3334af9 100644 --- a/templates/PatternUseReceipt.yaml +++ b/templates/PatternUseReceipt.yaml @@ -2,6 +2,7 @@ # Executable schema-v1 event validation/collection: activity-core # src/activity_core/context_resolvers/frontend_patterns.py (validate_event). # Consumer closeout integration and real-use acceptance remain open in T02. +# Local producer: scripts/reference_consume.py receipt (explicit JSON decision input). # Consumer produces immutable events; collector supplies independently verified origin. schema_version: 1 use_id: "::" diff --git a/tests/test_reference_consume.py b/tests/test_reference_consume.py new file mode 100644 index 0000000..42f1020 --- /dev/null +++ b/tests/test_reference_consume.py @@ -0,0 +1,176 @@ +"""Exercise qualified lookup, immutable event production and owner compatibility.""" +from copy import deepcopy +import hashlib +import json +from pathlib import Path +import shutil +import subprocess +import sys + +import pytest + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / 'scripts')) +import reference_consume as consume + + +@pytest.fixture(scope='module') +def published(): + return consume.published() + + +@pytest.fixture +def decision(): + return json.loads((ROOT / 'feedback/decision-input.example.json').read_text()) + + +def test_lookup_explains_filters_and_preserves_exact_read_revisions(published): + index, pin, manifest = published + result = consume.lookup(index, pin, manifest, ['CAND-0007'], ['recoverability']) + candidate, = result['candidates'] + assert result['status'] == 'matches' + assert candidate['id'] == 'CAND-0007' + assert candidate['classification']['forces']['worsens'] + assert candidate['gaps'] and candidate['maturity'] == 'hypothesis' + assert candidate['contract']['revision'] == manifest['corpus_revision'] + assert f'/commit/{manifest["corpus_revision"]}/' in candidate['contract']['url'] + assert all(e['locator']['revision'] == pin['packaging_revision'] for e in candidate['evidence']) + assert all(e['status'] == 'proposed' and e['condition'] and e['unknown'] for e in result['relations']) + assert all(v['revision'] == pin['packaging_revision'] for v in result['entrypoints'].values()) + assert result['reference']['manifest_sha256'] == pin['manifest_sha256'] + assert consume.lookup(index, pin, manifest, ['CAND-0007'], ['telemetry-quality'])['status'] == 'no-match' + + +def test_missing_context_abstains_and_responsibility_filters_intersect(published): + index, pin, manifest = published + result = consume.lookup(index, pin, manifest) + assert result['status'] == 'needs-context' and result['candidates'] == result['relations'] == [] + result = consume.lookup(index, pin, manifest, responsibilities=['mutation'], forces=['recoverability']) + assert result['candidates'] + assert all('mutation' in c['classification']['responsibilities'] and + 'recoverability' in c['classification']['forces']['improves'] for c in result['candidates']) + + +@pytest.mark.parametrize('filters', [ + {'candidates': ['CAND-0018']}, {'forces': ['magic']}, {'responsibilities': ['magic']}, +]) +def test_unknown_or_unpublished_identity_is_not_silently_selected(published, filters): + with pytest.raises(ValueError, match='Unknown'): + consume.lookup(*published, **filters) + + +def test_live_research_is_independent_and_tampered_export_fails(tmp_path, published): + shutil.copytree(ROOT / 'reference', tmp_path / 'reference') + (tmp_path / '.git').symlink_to(ROOT / '.git', target_is_directory=True) + live = tmp_path / 'research/candidates/MutationOutcomeTracking.md' + live.parent.mkdir(parents=True) + live.write_text('Legitimate later research outside the frozen release.\n') + assert consume.published(tmp_path) == published + export = tmp_path / 'reference/0.1/index.json' + export.write_text('{}\n') + with pytest.raises(ValueError, match='Published export changed'): + consume.published(tmp_path) + with pytest.raises(ValueError, match='Unknown reference version'): + consume.published(tmp_path, '9.9') + + +def test_receipt_keeps_unknown_outcome_and_pins_verified_reference(published, decision): + index, pin, _ = published + original = deepcopy(decision) + event = consume.receipt(decision, index, pin) + assert decision == original + assert event['kind'] == 'synthetic' + assert event['outcome']['value'] == 'unknown' + assert event['outcome']['observation'] is None + assert event['outcome']['measured_decision_minutes'] is None + assert event['reference']['packaging_revision'] == pin['packaging_revision'] + assert 'origin' not in event + + +@pytest.mark.parametrize('mutate', [ + lambda d: d.update(kind=''), + lambda d: d.update(recorded_at='2999-01-01T00:00:00Z'), + lambda d: d.update(recorded_at='2026-01-01T00:00:00'), + lambda d: d.update(previous_event_sha256='invalid'), + lambda d: d.update(origin={'verified': True}), + lambda d: d['consumer'].update(revision='main'), + lambda d: d['consumer'].update(run_ref=''), + lambda d: d['context'].update(considered=['CAND-0018']), + lambda d: d['context'].update(selected=['CAND-0007']), + lambda d: d['context'].update(considered=['CAND-0007', 'CAND-0007']), + lambda d: d['decision'].update(action='applied'), + lambda d: d['outcome'].update(value='helpful'), + lambda d: d['outcome'].update(measured_decision_minutes=-1), + lambda d: d['outcome'].update(measured_rework_minutes=float('nan')), +]) +def test_invalid_producer_claims_or_provenance_are_rejected(published, decision, mutate): + mutate(decision) + with pytest.raises(ValueError): + consume.receipt(decision, *published[:2]) + + +def test_followup_and_observed_result_retain_lineage(published, decision): + decision['previous_event_sha256'] = 'a' * 64 + decision['event_id'] = 'fixture-outcome-02' + decision['outcome'].update(value='mixed', basis='observed-result', stage='test', + observation='Disclosed fixture result; no consumer use.', evidence_refs=['fixture:test-01']) + event = consume.receipt(decision, *published[:2]) + assert event['previous_event_sha256'] == 'a' * 64 + assert event['outcome']['value'] == 'mixed' + + +def test_immutable_write_returns_exact_collector_index_entry(tmp_path, published, decision): + event = consume.receipt(decision, *published[:2]) + path = 'research/pattern-use/fixture/event-01.json' + entry = consume.write_receipt(event, path, tmp_path) + raw = (tmp_path / path).read_bytes() + assert json.loads(raw) == event + assert entry == {'path': path, 'sha256': hashlib.sha256(raw).hexdigest()} + with pytest.raises(FileExistsError): + consume.write_receipt(event, path, tmp_path) + assert (tmp_path / path).read_bytes() == raw + + +@pytest.mark.parametrize('path', ['/tmp/event.json', '../event.json', 'x//event.json', 'x/./event.json', 'x\\event.json']) +def test_unsafe_paths_are_rejected_before_writing(tmp_path, published, decision, path): + with pytest.raises(ValueError, match='Unsafe receipt path'): + consume.write_receipt(consume.receipt(decision, *published[:2]), path, tmp_path) + assert list(tmp_path.iterdir()) == [] + + +def test_symlink_escape_is_refused(tmp_path, published, decision): + (tmp_path / 'out').symlink_to(ROOT, target_is_directory=True) + with pytest.raises(ValueError, match='escapes consumer root'): + consume.write_receipt(consume.receipt(decision, *published[:2]), 'out/event.json', tmp_path) + + +def test_cli_generates_owner_accepted_event_and_index(tmp_path): + result = subprocess.run([sys.executable, str(ROOT / 'scripts/reference_consume.py'), '--version', '0.1', 'receipt', + '--input', str(ROOT / 'feedback/decision-input.example.json'), '--output', 'use/event.json'], + cwd=tmp_path, capture_output=True, text=True, check=True) + entry = json.loads(result.stdout) + assert entry['sha256'] == hashlib.sha256((tmp_path / entry['path']).read_bytes()).hexdigest() + owner = ROOT.parent / 'activity-core' + python = owner / '.venv/bin/python' + if not python.exists(): + pytest.skip('Actual activity-core contract check requires the sibling owner environment') + script = '''import json, sys +sys.path.insert(0, sys.argv[1]) +from activity_core.context_resolvers.frontend_patterns import validate_event +event = json.load(open(sys.argv[2])) +validate_event(event, event['consumer']['repo']) +''' + subprocess.run([str(python), '-B', '-c', script, str(owner / 'src'), str(tmp_path / entry['path'])], + check=True, capture_output=True, text=True) + + +def test_invalid_cli_decision_cannot_create_a_receipt(tmp_path, decision): + decision['decision']['action'] = 'applied' # Selection remains empty. + source = tmp_path / 'decision.json' + source.write_text(json.dumps(decision)) + result = subprocess.run([sys.executable, str(ROOT / 'scripts/reference_consume.py'), 'receipt', + '--input', str(source), '--output', 'use/event.json'], + cwd=tmp_path, capture_output=True, text=True) + assert result.returncode == 1 + assert 'Action/selection mismatch' in result.stderr + assert not (tmp_path / 'use').exists() diff --git a/workplans/FEP-WP-0008-use-driven-improvement.md b/workplans/FEP-WP-0008-use-driven-improvement.md index 3ef0c1d..c1fec35 100644 --- a/workplans/FEP-WP-0008-use-driven-improvement.md +++ b/workplans/FEP-WP-0008-use-driven-improvement.md @@ -58,6 +58,13 @@ Done: published/discoverable reference plus first real pinned decision note, and release validation survives legitimate new research. Registry entry/index are prepared in this design session; hosted registration and consumer integration remain open. +Residual acceptance received from FEP-WP-0010 (2026-10-04): the verified local +[lookup/citation helper](../scripts/reference_consume.py) and +[consumer commands](../feedback/ConsumerIntegration.md#local-lookup-and-closeout-helper) +remove local reference-navigation preparation. T01 still owns hosted registration, +common-template-owner integration into an actual eligible frontend task and its +first pinned decision note. No fixture or audit of this repo supplies that receipt. + ## Capture evidence of use and outcomes ```task @@ -87,6 +94,14 @@ rejected/no-match, delayed/corrected, duplicate and malformed paths tested as fi No fabricated real outcome to close the task. Pending cases get one automated artifact refresh after 14 days and remain unknown after 30; no manual survey dependency. +Residual acceptance received from FEP-WP-0010 (2026-10-04): the local producer now +fills verified release pins and writes a new schema-v1 JSON/YAML event with an exact +index digest; its synthetic event passes activity-core's actual validator. T02 retains +the consumer-owned automatic closeout invocation/index commit, authorized enrollment, +real use plus linked inspectable outcome, and delayed-artifact refresh. Collector +validation is not outcome truth or a producer-origin verification claim. Existing +collection remains with activity-core; no second collector or fictional use was added. + ## Run bounded improvements and monthly review autonomously ```task diff --git a/workplans/FEP-WP-0010-reference-consumption.md b/workplans/FEP-WP-0010-reference-consumption.md index c3f14a0..e1eb089 100644 --- a/workplans/FEP-WP-0010-reference-consumption.md +++ b/workplans/FEP-WP-0010-reference-consumption.md @@ -4,7 +4,7 @@ type: workplan title: "Close local reference-consumption gaps" domain: infotech repo: frontend-patterns -status: ready +status: finished owner: codex topic_slug: canon created: "2026-10-04" @@ -38,7 +38,7 @@ independent/held-out evidence. Name existing owners for the most relevant gaps. ```task id: FEP-WP-0010-T02 -status: todo +status: done priority: high state_hub_task_id: "52e73505-dadc-52b9-aedb-4ba0889e580c" ``` @@ -54,7 +54,7 @@ release validation and fail on tampering or unavailable pins. ```task id: FEP-WP-0010-T03 -status: todo +status: done priority: high state_hub_task_id: "08762144-141e-5f13-ab58-2054da04a221" ``` @@ -71,7 +71,7 @@ contract instead of creating a second collection service. ```task id: FEP-WP-0010-T04 -status: todo +status: done priority: high state_hub_task_id: "7efb810a-d6f8-540c-90a5-3c333e0bdbea" ``` @@ -89,3 +89,30 @@ WP-0003/0004 (broader/gated applicability) and WP-0006 (comparable windows). Add record references there. This plan may finish its local deliverable while the empirical owners remain open; do not claim those gaps closed or leave actionable residuals only in prose/history. + +## Completion — 2026-10-04 + +The local consume → cite → prepare-receipt bridge is delivered. `reference_consume.py` +verifies the immutable preview before filtering or receipt preparation; results carry +corpus/packaging pins, force costs, proposed relation conditions and explicit limits. +Input validation precedes exclusive source-relative writes; returned digests identify +the exact JSON/YAML bytes. The consumer retains its own decision and evidence authority. + +Validation: 104 tests pass with zero skips (31 consumption, 2 schema, 33 bench/admission, +22 evolution, 16 preview), including actual activity-core validator acceptance at +`3dd68d1`. Synthetic bench planning and evolution/classification/reference checks pass. +[Verification record](../docs/evidence/2026-10-04-reference-consumption.json) preserves +owner hash and boundaries. No paid inference, production mutation, enrolled consumer, +new empirical learning cycle, canonical admission or usefulness claim was introduced. + +Residual acceptance was handed to the existing live WP-0008 T01/T02 descriptions +before finish: actual hosted/template/consumer decision integration, hook invocation, +index/enrollment, real use plus outcome and delayed refresh. WP-0009 retains independent +human/fresh replication/M1; WP-0003/0004 retains contrasting/gated applicability; +WP-0006 retains useful comparable returns evidence. Those existing obligations are not +new duplicate tasks and are not closed by the bridge's disclosed development cases. +Scope/assessment and capability guidance now describe this separation accurately. + +State Hub implementation decision: `6914093c-e540-4d31-a1ea-607021c5e185`; plan UUID +`c46b02ca-26a8-5313-b0f2-eb0385ff820c`. Registration assigned all task UUIDs and verified +the initial pushed plan on primary/railiance01; final source statuses are synced at close.