-
Notifications
You must be signed in to change notification settings - Fork 0
feat(evidence): make connector truncation explicit #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
huygdv
wants to merge
14
commits into
main
Choose a base branch
from
agent/source-artifact-safety
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
430e2e9
feat(evidence): add connector artifact safety contract
huygdv f5bec5c
test(evidence): cover connector truncation cases
huygdv 4866499
feat(evidence): validate connector capture provenance
huygdv 4cff786
docs(evidence): define source artifact safety contract
huygdv 75cc133
docs(agent): require full evidence behind truncated previews
huygdv 802090a
docs(live): document connector evidence safety
huygdv d64a456
ci(evidence): test source artifact contract
huygdv f687fd4
ci(pages): gate deploy on source artifact checks
huygdv 1dcd4ed
feat(evidence): add standalone source artifact validator
huygdv f8d335d
test(evidence): cover pack-level source validation
huygdv b566e16
feat(evidence): gate match-day writes on full source artifacts
huygdv 21ef081
refactor(evidence): keep provenance validation standalone
huygdv 6e291c5
ci(evidence): preserve existing semantic gate unchanged
huygdv 9469811
ci(evidence): add standalone source artifact gate
huygdv File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| name: source-artifacts | ||
|
|
||
| on: | ||
| push: | ||
| paths: | ||
| - "packs/**" | ||
| - "scripts/add-match-day.mjs" | ||
| - "scripts/source-artifact.mjs" | ||
| - "scripts/source-artifact.test.mjs" | ||
| - "scripts/validate-source-artifacts.mjs" | ||
| - ".github/workflows/source-artifacts.yml" | ||
| pull_request: | ||
| paths: | ||
| - "packs/**" | ||
| - "scripts/add-match-day.mjs" | ||
| - "scripts/source-artifact.mjs" | ||
| - "scripts/source-artifact.test.mjs" | ||
| - "scripts/validate-source-artifacts.mjs" | ||
| - ".github/workflows/source-artifacts.yml" | ||
|
|
||
| jobs: | ||
| validate: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: "20" | ||
| - name: Test source artifact contract | ||
| run: node scripts/source-artifact.test.mjs | ||
| - name: Validate source captures in all packs | ||
| run: node scripts/validate-source-artifacts.mjs |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| # Source Artifact Safety Contract | ||
|
|
||
| Status: v1 | ||
| Scope: evidence obtained through tools/connectors before it becomes public Causari data. | ||
|
|
||
| ## Problem | ||
|
|
||
| Connector and tool responses may be truncated to fit an agent context. A preview is useful for routing, but it is unsafe as the sole basis for a public factual claim because omitted rows or fields may change the conclusion. | ||
|
|
||
| Causari therefore distinguishes: | ||
|
|
||
| ```text | ||
| direct, independently retrievable citation | ||
| vs. | ||
| connector capture with explicit completeness metadata | ||
| ``` | ||
|
|
||
| The pack remains deterministic. This contract does not add another agent loop; it only controls what evidence an updater may trust. | ||
|
|
||
| ## Rules | ||
|
|
||
| 1. A normal authoritative URL may remain a legacy source object with `type`, `citation`, and `url`. | ||
| 2. Evidence obtained through a connector should include `capture.mode: "connector"`. | ||
| 3. Connector evidence must state `truncated: true|false`. Missing means unknown and fails validation. | ||
| 4. A truncated response must retain a full artifact with: | ||
| - stable URI or content location; | ||
| - SHA-256 checksum; | ||
| - full payload byte length; | ||
| - bounded preview; | ||
| - retrieval instructions. | ||
| 5. An agent must read the full artifact or independently verify the claim at the authoritative URL before publishing. | ||
| 6. The artifact itself should not be copied into `events.json`; the pack stores only provenance metadata. | ||
| 7. Secrets, access tokens, cookies, and private headers must never be retained in previews or artifacts. | ||
|
|
||
| ## Source examples | ||
|
|
||
| ### Direct source | ||
|
|
||
| ```json | ||
| { | ||
| "type": "official", | ||
| "citation": "FIFA — World Cup 2026 match centre", | ||
| "url": "https://www.fifa.com/..." | ||
| } | ||
| ``` | ||
|
|
||
| ### Complete connector response | ||
|
|
||
| ```json | ||
| { | ||
| "type": "official", | ||
| "citation": "FIFA — World Cup 2026 match centre", | ||
| "url": "https://www.fifa.com/...", | ||
| "capture": { | ||
| "mode": "connector", | ||
| "truncated": false, | ||
| "capturedAt": "2026-07-15T01:05:00Z" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Truncated connector response with retained artifact | ||
|
|
||
| ```json | ||
| { | ||
| "type": "official", | ||
| "citation": "FIFA — World Cup 2026 match centre", | ||
| "url": "https://www.fifa.com/...", | ||
| "capture": { | ||
| "mode": "connector", | ||
| "truncated": true, | ||
| "capturedAt": "2026-07-15T01:05:00Z", | ||
| "artifact": { | ||
| "uri": "artifact://connector/fifa-2026-07-15.json", | ||
| "sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", | ||
| "byteLength": 120345, | ||
| "preview": "{\"matches\":[...]}", | ||
| "retrievalHint": "Read the stored JSON artifact before verifying scoreline claims." | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Validation | ||
|
|
||
| `validate-pack.mjs` calls `validateSourceCapture` for every event source. Existing direct citations remain backward compatible. Only a source that opts into connector capture is subject to the new completeness rules. | ||
|
|
||
| Run: | ||
|
|
||
| ```bash | ||
| node scripts/source-artifact.test.mjs | ||
| node scripts/validate-pack.mjs | ||
| ``` | ||
|
|
||
| This contract protects provenance completeness. It does not prove that a source is authoritative or that the causal interpretation is correct; the honesty and causal-quality gates still apply. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| #!/usr/bin/env node | ||
|
|
||
| const SHA256 = /^[a-f0-9]{64}$/i; | ||
|
|
||
| function isObject(value) { | ||
| return value !== null && typeof value === 'object' && !Array.isArray(value); | ||
| } | ||
|
|
||
| function isNonEmptyString(value) { | ||
| return typeof value === 'string' && value.trim().length > 0; | ||
| } | ||
|
|
||
| function isNonNegativeInteger(value) { | ||
| return Number.isInteger(value) && value >= 0; | ||
| } | ||
|
|
||
| function validIsoDate(value) { | ||
| return isNonEmptyString(value) && !Number.isNaN(Date.parse(value)); | ||
| } | ||
|
|
||
| function validateArtifact(artifact, label) { | ||
| const errors = []; | ||
|
|
||
| if (!isObject(artifact)) { | ||
| return [`${label}.artifact must be an object when connector output is truncated`]; | ||
| } | ||
| if (!isNonEmptyString(artifact.uri)) { | ||
| errors.push(`${label}.artifact.uri must point to the full stored payload`); | ||
| } | ||
| if (!isNonEmptyString(artifact.sha256) || !SHA256.test(artifact.sha256)) { | ||
| errors.push(`${label}.artifact.sha256 must be a 64-character SHA-256 hex digest`); | ||
| } | ||
| if (!isNonNegativeInteger(artifact.byteLength)) { | ||
| errors.push(`${label}.artifact.byteLength must be a non-negative integer`); | ||
| } | ||
| if (!isNonEmptyString(artifact.preview)) { | ||
| errors.push(`${label}.artifact.preview must be a non-empty bounded preview`); | ||
| } | ||
| if (!isNonEmptyString(artifact.retrievalHint)) { | ||
| errors.push(`${label}.artifact.retrievalHint must explain how to read the full payload`); | ||
| } | ||
|
|
||
| return errors; | ||
| } | ||
|
|
||
| /** | ||
| * Validate optional evidence-capture metadata attached to an event source. | ||
| * | ||
| * Legacy/direct URL citations do not need `capture`. Once evidence is obtained | ||
| * through a connector, `capture.mode = "connector"` makes truncation explicit. | ||
| * A truncated connector response is invalid unless the full payload is stored | ||
| * and addressable by a checksummed artifact reference. | ||
| */ | ||
| export function validateSourceCapture(source, label = 'source') { | ||
| const errors = []; | ||
|
|
||
| if (!isObject(source)) { | ||
| return [`${label} must be an object`]; | ||
| } | ||
|
|
||
| const capture = source.capture; | ||
| if (capture === undefined) return errors; | ||
| if (!isObject(capture)) return [`${label}.capture must be an object`]; | ||
|
|
||
| if (!['direct', 'connector'].includes(capture.mode)) { | ||
| errors.push(`${label}.capture.mode must be "direct" or "connector"`); | ||
| return errors; | ||
| } | ||
|
|
||
| if (capture.capturedAt !== undefined && !validIsoDate(capture.capturedAt)) { | ||
| errors.push(`${label}.capture.capturedAt must be an ISO date-time`); | ||
| } | ||
|
|
||
| if (capture.mode === 'direct') { | ||
| if (capture.truncated === true) { | ||
| errors.push(`${label}.capture: direct evidence cannot claim a truncated connector payload`); | ||
| } | ||
| return errors; | ||
| } | ||
|
|
||
| if (typeof capture.truncated !== 'boolean') { | ||
| errors.push(`${label}.capture.truncated must be an explicit boolean for connector evidence`); | ||
| return errors; | ||
| } | ||
|
|
||
| if (capture.truncated === true) { | ||
| errors.push(...validateArtifact(capture.artifact, label)); | ||
| } else if (capture.artifact !== undefined) { | ||
| errors.push(...validateArtifact(capture.artifact, label)); | ||
| } | ||
|
|
||
| return errors; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For pushes to
mainthat changepacks/**, this Pages gate still only runs the unit test plusvalidate-pack;validatePackDatadoes not inspectevent.sources, while the new CLI that rejects truncated connector captures isscripts/validate-source-artifacts.mjs. Since the deploy job depends only on this workflow'svalidatejob, an invalid connector capture can still be published to Pages even if the separatesource-artifactsworkflow fails, so addnode scripts/validate-source-artifacts.mjshere or makevalidate-packcall it before deploy.Useful? React with 👍 / 👎.