Add verified reference lookup and immutable consumer receipts
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a1045b-933d-7aa1-b2db-104557487a79
This commit is contained in:
parent
c7f87b791b
commit
bd85fcd179
15 changed files with 583 additions and 11 deletions
4
Makefile
4
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
|
||||
|
|
|
|||
4
SCOPE.md
4
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 |
|
||||
|
|
|
|||
17
START.md
17
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;
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
52
docs/evidence/2026-10-04-reference-consumption.json
Normal file
52
docs/evidence/2026-10-04-reference-consumption.json
Normal file
|
|
@ -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."
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
28
feedback/decision-input.example.json
Normal file
28
feedback/decision-input.example.json
Normal file
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
176
scripts/reference_consume.py
Normal file
176
scripts/reference_consume.py
Normal file
|
|
@ -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()
|
||||
|
|
@ -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: "<consumer-repo>:<task-id>:<decision-id>"
|
||||
|
|
|
|||
176
tests/test_reference_consume.py
Normal file
176
tests/test_reference_consume.py
Normal file
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue