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
35 changes: 35 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -347,3 +347,38 @@ jobs:
path: ${{ github.workspace }}/scry-self-analysis.html
if-no-files-found: error
retention-days: 14
# FEAT-073 self-adjudication harness — OBSERVATION MODE.
#
# FEAT-025 (above) dogfoods scry on its own module at ONE commit. This
# extends the same instrument across TIME: build scry_mcdc.wasm at the
# last few commits and compare consecutive pairs BY STABLE OBLIGATION
# IDENTITY (FEAT-064/072).
#
# It DOES NOT GATE, and that is deliberate, not an oversight (DD-022).
# The adjudicator this would drive was refuted in review (scry#122): an
# adjudicator that can wrongly report a fix is worse than none, because
# the cheapest way for an agent to satisfy it is to delete the code. So
# the harness reports and never judges — `continue-on-error` is belt to
# the script's braces, which exits 0 unconditionally by construction.
#
# This already paid for itself: its first run surfaced scry#123 (43-45%
# of function identities churn per build via the Rust symbol
# disambiguator), a defect no fixture suite could have found because it
# lives in the toolchain's output rather than in scry's logic.
#
# It graduates to a gate only when REQ-021 closes.
- name: Self-adjudication harness (FEAT-073, observation only — never gates)
continue-on-error: true
run: |
git fetch --deepen=5 origin "$GITHUB_SHA" 2>/dev/null || true
bash scripts/self-history.sh 3 "${{ github.workspace }}/self-history"
- name: Upload self-history observation
uses: actions/upload-artifact@v4
with:
name: scry-self-history
# if-no-files-found: warn — an observation that produced nothing is a
# fact to report, not a build failure. `error` here would turn the
# non-gating harness into a gate through the back door.
if-no-files-found: warn
path: ${{ github.workspace }}/self-history
retention-days: 14
29 changes: 29 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,35 @@ jobs:
-o "$GITHUB_WORKSPACE/dist/self-analysis.html" \
--title "scry ${VERSION} — self-analysis (scry_mcdc.wasm)"

# FEAT-072: a release-over-release delta, keyed on stable obligation
# identity. Answers "what actually moved since the last release?" — the
# question the single-snapshot dashboard has never been able to answer,
# because (func_index, pc) shifts on every edit.
#
# It reports what CHANGED and asserts no verdict (scry#122 / DD-022); the
# page carries that constraint in its own copy and a unit test greps for
# it. `continue-on-error` because a missing or unbuildable previous tag
# is a fact about the history, not a reason to block a release — the
# landing page links this view only when it exists.
- name: Render release delta vs the previous tag (FEAT-072)
continue-on-error: true
run: |
set -uo pipefail
PREV="$(git tag --list 'v*' --sort=-v:refname | grep -v "^${VERSION}$" | head -1)"
if [[ -z "$PREV" ]]; then
echo "no previous tag — skipping the delta view (first release)"; exit 0
fi
echo "delta: $PREV -> $VERSION"
WT="$(mktemp -d)/prev"
git worktree add --detach -q "$WT" "$PREV" || exit 0
( cd "$WT/crates/scry-mcdc" && cargo build --release --target wasm32-wasip1 -q ) || exit 0
cargo run --release -p scry-sai-viz -- delta \
"$WT/crates/scry-mcdc/target/wasm32-wasip1/release/scry_mcdc.wasm" \
crates/scry-mcdc/target/wasm32-wasip1/release/scry_mcdc.wasm \
-o "$GITHUB_WORKSPACE/dist/delta.html" \
--title "${PREV} → ${VERSION} (by stable obligation identity)"
git worktree remove --force "$WT" 2>/dev/null || true

- name: Build landing page (scry-viz index)
run: |
set -euo pipefail
Expand Down
253 changes: 253 additions & 0 deletions artifacts/design.yaml

Large diffs are not rendered by default.

225 changes: 208 additions & 17 deletions artifacts/roadmap-3.0.yaml

Large diffs are not rendered by default.

76 changes: 76 additions & 0 deletions crates/scry-analyze-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,29 @@ pub struct Advisory {
/// Empty when no identity could be derived. Opaque by construction — the
/// layout is not a contract; do not parse it.
pub obligation_id: String,
/// FEAT-072 (DD-021): identity of the SITE, excluding the advisory code —
/// so it is STABLE when an obligation changes state. A `div-by-zero`
/// becoming `proven-safe` changes `obligation_id` but NOT this, which is
/// what lets a delta view report "this site changed state" instead of
/// "one identity vanished and an unrelated one appeared".
///
/// Derived here as DATA. It is deliberately NOT paired with an adjudicator
/// in this release: matching on this key alone was refuted in review
/// (scry#122), because a positional ordinal can be inherited by a
/// surviving sibling. Consumers may group and diff by it; nothing in this
/// release may conclude "discharged" from it.
pub site_key: String,
/// FEAT-072 (DD-021): the ORDINAL DOMAIN this site's ordinal is counted
/// within (function + region path + operator kind), WITHOUT the ordinal.
///
/// This is the aliasing signal made observable: `ObligationId.v` proves a
/// surviving site can inherit a deleted sibling's ordinal
/// (survivor_inherits_deleted_identity), and that can only happen inside
/// one group. Comparing a group's membership across two runs therefore
/// bounds where identity may have shifted. Necessary but NOT sufficient to
/// rule aliasing out — the pairing of a deletion with a same-kind insertion
/// leaves the group unchanged, which is precisely what falsified DD-021.
pub group_key: String,
}

/// FEAT-055 (REQ-018): a candidate counterexample for an `UnprovenObligation`
Expand Down Expand Up @@ -3107,6 +3130,45 @@ fn obligation_id_of(func_ident: &str, path: &str, kind: &str, ordinal: u32, code
out
}

/// FEAT-072 (DD-021): the SITE key — everything the obligation id has EXCEPT
/// the advisory code, so it survives an obligation changing state.
fn site_key_of(func_ident: &str, path: &str, kind: &str, ordinal: u32) -> String {
let mut h = Sha256::new();
h.update(b"site|");
h.update(func_ident.as_bytes());
h.update(b"|");
h.update(path.as_bytes());
h.update(b"|");
h.update(kind.as_bytes());
h.update(b"|");
h.update(ordinal.to_le_bytes());
let d = h.finalize();
let mut out = String::with_capacity(16);
for b in d.iter().take(8) {
out.push_str(&format!("{b:02x}"));
}
out
}

/// FEAT-072 (DD-021): the ORDINAL DOMAIN key — function + region path + kind,
/// WITHOUT the ordinal. Aliasing can only occur WITHIN one such domain, so a
/// change in a group's membership bounds where identity may have shifted.
fn group_key_of(func_ident: &str, path: &str, kind: &str) -> String {
let mut h = Sha256::new();
h.update(b"group|");
h.update(func_ident.as_bytes());
h.update(b"|");
h.update(path.as_bytes());
h.update(b"|");
h.update(kind.as_bytes());
let d = h.finalize();
let mut out = String::with_capacity(16);
for b in d.iter().take(8) {
out.push_str(&format!("{b:02x}"));
}
out
}

/// FEAT-064: does this advisory describe the MODULE rather than a code site?
/// Such advisories carry `(func 0, pc 0)` as a SENTINEL, so giving them a site
/// identity would collide with a genuine advisory there, drift whenever func 0's
Expand All @@ -3126,6 +3188,8 @@ fn stamp_obligation_ids(
) {
for a in advisories.iter_mut().filter(|a| is_module_scoped(&a.code)) {
a.obligation_id = obligation_id_of("<module>", "", "<module>", 0, &a.code);
a.site_key = site_key_of("<module>", "", "<module>", 0);
a.group_key = group_key_of("<module>", "", "<module>");
}
for f in defined_funcs {
let ident = function_meta
Expand All @@ -3145,6 +3209,8 @@ fn stamp_obligation_ids(
.map(op_report_name)
.unwrap_or_else(|| a.code.clone());
a.obligation_id = obligation_id_of(&ident, path, &kind, *ordinal, &a.code);
a.site_key = site_key_of(&ident, path, &kind, *ordinal);
a.group_key = group_key_of(&ident, path, &kind);
}
}
}
Expand Down Expand Up @@ -3188,6 +3254,8 @@ fn compute_advisories(
verification: "re-run scry: this handle_findings entry disappears".into(),
counterexample: None,
obligation_id: String::new(),
site_key: String::new(),
group_key: String::new(),
});
}

Expand Down Expand Up @@ -3228,6 +3296,8 @@ fn compute_advisories(
),
counterexample: Some(trap_counterexample(t.kind, &t.op, memory_size_bytes)),
obligation_id: String::new(),
site_key: String::new(),
group_key: String::new(),
});
}
TrapVerdict::ProvenSafe => {
Expand Down Expand Up @@ -3255,6 +3325,8 @@ fn compute_advisories(
),
counterexample: None,
obligation_id: String::new(),
site_key: String::new(),
group_key: String::new(),
});
}
}
Expand Down Expand Up @@ -3296,6 +3368,8 @@ fn compute_advisories(
),
counterexample: None,
obligation_id: String::new(),
site_key: String::new(),
group_key: String::new(),
});
}

Expand All @@ -3315,6 +3389,8 @@ fn compute_advisories(
verification: "re-run scry: stack_usage.max_stack_bytes becomes Bytes(n)".into(),
counterexample: None,
obligation_id: String::new(),
site_key: String::new(),
group_key: String::new(),
});
}

Expand Down
42 changes: 35 additions & 7 deletions crates/scry-mcdc/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading