Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .machine_readable/REGISTRY.a2ml
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ name = "DYADT — Did-You-Actually-Do-That"
stream = "governance"
home = "did-you-actually-do-that/"
canonical_doc = "did-you-actually-do-that/README.adoc"
source_hash = "sha256:445359ddcc92b56dfc8e8a3bdc16062439f1236b5fd0f42099113e7afa86d2e0"
source_hash = "sha256:2ae635b9ede51e76781cb7c171108f2a4505b0aae9ac97fb05c910915141eb2a"
route = "post-action agent-claim verification (Tier 4 accountability)"

[[spec]]
Expand Down
39 changes: 39 additions & 0 deletions did-you-actually-do-that/spec/VERIFICATION-PROTOCOL.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,45 @@ discharged by `command-transcript`.
NOT fall back to reading the agent's cited evidence.
. *Stale evidence is unverifiable.* If `not_before` is set and the primary
evidence predates it, the verdict is `unverifiable` (reason `stale-evidence`).
A verifier that does not collect evidence timestamps MUST return
`unverifiable` for any claim carrying `not_before` (it cannot honour the
guarantee) rather than verify possibly-stale evidence as fresh.

== Fail-safe requirements (normative)

Every place a verifier *cannot* collect trustworthy primary evidence is
`unverifiable`, never a confident wrong verdict. A conforming verifier MUST
return `unverifiable` (not `confirmed`/`refuted`) when:

* the diff base ref is missing or does not resolve (`no-base-ref`) for a
`created`/`modified`/`deleted` claim;
* a required field (`claim_class`, `target`, `expect`, `verifier`) is absent
(`missing-field`), or a `[[claim]]` block carries no `id` (it MUST still
appear in the output — no silent drop);
* the `target` of a file verifier is an absolute path, contains `..`, or is a
symlink (`unsafe-path` — evidence could be redirected);
* an `expect` predicate is empty or otherwise always-matching
(`empty-pattern`), or its regex is malformed (`bad-regex`);
* a `contains:`/`sha256:` target is not a readable regular file
(`not-a-regular-file` / `unreadable`).

And a licence/SPDX claim — detected in ANY of `claim_class`, `target`,
`expect`, or `statement` — MUST be `unverifiable` with reason `manual-only`
(the Manual-Only licence policy; never auto-`confirmed`).

== Command execution & sandboxing (normative)

The `command-transcript` verifier EXECUTES the claim's `target`. Consequences:

* `stdout-contains:` MUST match the command's *stdout only*; stderr MUST be
captured separately so a marker printed to stderr cannot false-confirm.
* Because the claims file is agent-authored, executing its commands is code
execution. A verifier running over UNTRUSTED claims MUST sandbox the command
(no network, constrained filesystem, resource limits). The reference verifier
does NOT sandbox and is for TRUSTED (repo-authored, PR-reviewed) claims only;
a conforming production verifier MUST document its execution boundary.
* `claims-compose` recursion MUST be depth-bounded to defeat self- or
mutually-referential claim files (the reference verifier caps depth at 8).

== Multi-verifier corroboration (SHOULD)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[claims]
schema = "dyadt/claim@1"
actor = "conformance"
[[claim]]
id = "C1"
claim_class = "command-ran"
statement = "added the SPDX-License-Identifier header"
target = "true"
expect = "exit==0"
verifier = "command-transcript"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
C1 unverifiable
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[claims]
schema = "dyadt/claim@1"
actor = "conformance"
[[claim]]
id = "C1"
claim_class = "command-ran"
statement = "a claim missing its target field"
expect = "exit==0"
verifier = "command-transcript"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
C1 unverifiable
10 changes: 10 additions & 0 deletions did-you-actually-do-that/spec/conformance/unsafe-path.a2ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[claims]
schema = "dyadt/claim@1"
actor = "conformance"
[[claim]]
id = "C1"
claim_class = "file-changed"
statement = "a path-traversal target"
target = "../../etc/passwd"
expect = "created"
verifier = "git-diff"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
C1 unverifiable
70 changes: 70 additions & 0 deletions scripts/tests/wave4-dyadt-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,76 @@
EOF
( cd "$ROOT" && bash "$V" "$TMP/g.a2ml" >/dev/null 2>&1 ); [ $? -eq 0 ] && ok "all-confirmed file exits 0" || bad "all-confirmed file did not exit 0"

echo "== hardening (adversarial-review fixes) =="
mk() { printf '%s\n' "$2" > "$TMP/$1"; }

Check warning on line 111 in scripts/tests/wave4-dyadt-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AZ8lxB10-Fn6_ZYvCuSR&open=AZ8lxB10-Fn6_ZYvCuSR&pullRequest=458

Check warning on line 111 in scripts/tests/wave4-dyadt-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AZ8lxB10-Fn6_ZYvCuST&open=AZ8lxB10-Fn6_ZYvCuST&pullRequest=458

Check warning on line 111 in scripts/tests/wave4-dyadt-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AZ8lxB10-Fn6_ZYvCuSS&open=AZ8lxB10-Fn6_ZYvCuSS&pullRequest=458
reason_of() { # file id

Check warning on line 112 in scripts/tests/wave4-dyadt-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AZ8lxB10-Fn6_ZYvCuSU&open=AZ8lxB10-Fn6_ZYvCuSU&pullRequest=458
cd "$ROOT" && DYADT_ALLOW_UNVERIFIABLE=1 bash "$V" "$1" 2>/dev/null \

Check warning on line 113 in scripts/tests/wave4-dyadt-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AZ8lxB10-Fn6_ZYvCuSV&open=AZ8lxB10-Fn6_ZYvCuSV&pullRequest=458
| grep -E "$2|<block" | grep -oE '(confirmed|REFUTED|unverifiable) *\[[^]]*\] *[a-z-]+' | head -1

Check warning on line 114 in scripts/tests/wave4-dyadt-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AZ8lxB10-Fn6_ZYvCuSW&open=AZ8lxB10-Fn6_ZYvCuSW&pullRequest=458
}
# missing required field -> unverifiable, still counted
mk mf.a2ml '[claims]
[[claim]]
id = "C1"
claim_class = "command-ran"
expect = "exit==0"
verifier = "command-transcript"'
[[ "$(reason_of "$TMP/mf.a2ml" C1)" == unverifiable*missing-field ]] && ok "missing field -> unverifiable" || bad "missing field not caught"
# claim with no id is NOT silently dropped (appears as a block, unverifiable)
mk noid.a2ml '[claims]
[[claim]]
claim_class = "command-ran"
target = "true"
expect = "exit==0"
verifier = "command-transcript"'
( cd "$ROOT" && DYADT_ALLOW_UNVERIFIABLE=1 bash "$V" "$TMP/noid.a2ml" 2>/dev/null | grep -q 'no-id' ) && ok "missing id not silently dropped" || bad "missing id was dropped"
# empty pattern -> unverifiable (not an always-match confirm)
mk ep.a2ml '[claims]
[[claim]]
id = "C1"
claim_class = "file-changed"
target = "README.adoc"
expect = "contains:"
verifier = "git-diff"'
[[ "$(reason_of "$TMP/ep.a2ml" C1)" == unverifiable*empty-pattern ]] && ok "empty contains pattern -> unverifiable" || bad "empty pattern not caught"
# path traversal -> unverifiable
mk up.a2ml '[claims]
[[claim]]
id = "C1"
claim_class = "file-changed"
target = "../etc/passwd"
expect = "created"
verifier = "git-diff"'
[[ "$(reason_of "$TMP/up.a2ml" C1)" == unverifiable*unsafe-path ]] && ok "path traversal -> unverifiable" || bad "unsafe path not caught"
# unresolvable base -> unverifiable (not confident-wrong created)
mk nb.a2ml '[claims]
[[claim]]
id = "C1"
claim_class = "file-changed"
target = "README.adoc"
expect = "created"
verifier = "git-diff"'
r="$(cd "$ROOT" && DYADT_BASE=definitely-not-a-ref DYADT_ALLOW_UNVERIFIABLE=1 bash "$V" "$TMP/nb.a2ml" 2>/dev/null | grep -oE 'unverifiable *\[[^]]*\] *[a-z-]+' | head -1)"
[[ "$r" == unverifiable*no-base-ref ]] && ok "unresolvable base -> unverifiable" || bad "unresolvable base not caught (got: $r)"
# stderr marker must NOT confirm a stdout-contains claim
mk se.a2ml '[claims]
[[claim]]
id = "C1"
claim_class = "command-ran"
target = "echo marker >&2; true"
expect = "stdout-contains:marker"
verifier = "command-transcript"'
[[ "$(reason_of "$TMP/se.a2ml" C1)" == REFUTED* ]] && ok "stderr does not satisfy stdout-contains" || bad "stderr false-confirmed stdout claim"
# licence claim phrased only in the statement is still manual-only
mk lic.a2ml '[claims]
[[claim]]
id = "C1"
claim_class = "command-ran"
statement = "added the SPDX licence header"
target = "true"
expect = "exit==0"
verifier = "command-transcript"'
[[ "$(reason_of "$TMP/lic.a2ml" C1)" == unverifiable*manual-only ]] && ok "licence-in-statement -> manual-only" || bad "licence-in-statement auto-confirmed"

echo "== conformance suite =="
bash "$ROOT/did-you-actually-do-that/spec/conformance/run-conformance.sh" >/dev/null 2>&1 && ok "conformance vectors pass" || bad "conformance vectors failed"

Expand Down
Loading
Loading