Skip to content

fix(runtime): sanitize underscore-separated reference labels - #284

Merged
aviggiano merged 2 commits into
mainfrom
fix/lens-underscore-reference-labels
Aug 6, 2026
Merged

aviggiano merged 2 commits into
mainfrom
fix/lens-underscore-reference-labels

Conversation

@aviggiano

@aviggiano aviggiano commented Aug 6, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

The failure

isUnsupportedExternalReferenceLabel (packages/runtime/src/artifact-gates.ts) was:

return /^[A-Z][A-Z0-9]*-\d+$/u.test(expectationId);

[A-Z0-9]* cannot span an underscore, so LEND_ACC_01 did not match. The sanitizer's guard then bailed out for that property, the ID survived into verifyPropertyProvenanceArtifacts, and it was reported as PROPERTY_REFERENCE_EXPECTATION_UNAUTHORIZED at severity error — failing the node.

LEND-01 in the same position would have been stripped silently. So whether a lens node survived turned on the model's choice of punctuation.

R44 (aave-v4-v0012-main-issue272-r44-invariant-only, image im-01KZBJ546AZANSE7RNBY35A9DG), node property-specification-0kn0t at 2026-08-06T14:49Z:

Property lens expectation "LEND_ACC_01" is not present in a supplied pinned-reference catalog;
Property lens expectation "LEND_ACC_02" is not present in a supplied pinned-reference catalog;

Run state: 19 succeeded / 2 failed / 11 pending, run failed, property-specification-fanin never ran. Because fan-in depends on all eight lenses, one node failing this way blocks fan-in, invariant setup, Recon, findings and report.md.

The fix

Allow _ as well as - as the separator:

return /^[A-Z][A-Z0-9_]*[-_]\d+$/u.test(expectationId);

This is reached only for identifiers already absent from every supplied catalog, so no authorized expectation changes behaviour.

Validation

  • regression proven red without the source change and green with it; the red run reproduces R44's exact message for LEND_ACC_01
  • asserts the underscore ID is sanitized alongside a hyphenated one, that no PROPERTY_REFERENCE_EXPECTATION_UNAUTHORIZED is raised, and that the node does not fail
  • all four property-lens sanitizer tests pass
  • typecheck, eslint, prettier, git diff --check clean

Worth deciding separately

This makes the existing behaviour consistent rather than settling what it should be. An unauthorized reference expectation is currently sanitized in some cases and fatal in others, and that split was an accident of the regex. Whichever is intended — drop a citation that does not resolve, or reject an artifact that fabricates one — should apply uniformly. I have kept the existing sanitize-and-continue behaviour and only made it consistent, rather than changing the policy unreviewed.

Related

Greptile Summary

The PR broadens unsupported external-reference label recognition so underscore-separated and multi-segment catalog labels are sanitized consistently before provenance verification.

  • Supports mixed hyphen and underscore segments with a numeric suffix.
  • Preserves fatal verification for forged namespaced citations.
  • Adds regressions for underscore, multi-segment, and mixed forged-citation cases.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
packages/runtime/src/artifact-gates.ts Broadens external catalog-label matching while retaining the existing authority boundary for namespaced citations.
packages/runtime/test/artifact-gates.test.ts Adds focused regression coverage for multi-segment labels, underscore-separated labels, and intentionally rejected namespaced citations.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Property lens artifact] --> B[Read supplied catalog IDs]
  B --> C{Unauthorized ID shape}
  C -->|External catalog label| D[Remove unauthorized reference]
  D --> E[Refresh verification digest]
  E --> F[Continue node successfully]
  C -->|Forged namespaced citation| G[Preserve artifact]
  G --> H[Provenance verifier reports unauthorized citation]
  H --> I[Fail node]
Loading

Reviews (3): Last reviewed commit: "fix(runtime): match multi-segment catalo..." | Re-trigger Greptile

Context used:

@aviggiano
aviggiano marked this pull request as draft August 6, 2026 15:30
@aviggiano

Copy link
Copy Markdown
Collaborator Author

Held as draft — a reviewer challenges the policy, not the change

Two reviews. One recommended merge with two P2s (one taken, one correctly rejected — see below). The second raised a P1 that I verified in part and that changes what this PR should be.

What I verified

The reviewer's factual claims hold:

  • The lens prompt tells the model to copy identifiers verbatim: "Populate reference_expectations only from exact identifiers present in the supplied pinned-reference artifacts... carry the supplied identifier unchanged" (.ultrafuzz/prompts/properties/0kn0t-lens.md:31-34).
  • The 0kn0t pinned reference is a Markdown corpus (0kn0t/properties_framework: README.md, 01_PROPERTY_CLASSIFICATION_TAXONOMY.md, …) — not a structured ultrafuzz/reference-expectations@1 catalogue.
  • R44's config supplies no expectation catalogue: keys are app_name, benchmark_execution, braintrust, eval_reporting, ground_truth, image_name, models, node_timeout_seconds, run_id, schema_version, target.
  • readLensSuppliedExpectationIds returns an empty id set with no diagnostic when no dependency declares such a catalogue (artifact-gates.ts:1810, if (expectationPaths.length === 0) continue;).

So in the configuration these runs actually use, every citation any lens emits is "unauthorized", and the reviewer is right that R44's twelve LEND_* identifiers were not fabricated.

Why I am not making the change the reviewer asks for

The reviewer's remedy is to treat "no catalogue supplied" as unverifiable — warn, keep the identifiers, do not rewrite the lens. I implemented exactly that. It fails seven existing tests, because the current behaviour is deliberate and pinned:

  • property lens gate strips unsupported external labels without weakening benchmark authority
  • property lens authority sanitizer applies to custom logical lens IDs
  • and five others

Those tests construct nodes with no catalogue and assert that doc-shaped labels are stripped with a PROPERTY_REFERENCE_EXPECTATION_SANITIZED warning, while a namespaced mapping like benchmark:unexpected does fail the node. That is a coherent policy: reference_expectations is meant to carry verifiable provenance, so an identifier that cannot be authorised is dropped rather than presented as if it were, and a forged benchmark mapping fails loudly.

Under that policy this PR is aligned with intent and merely makes it consistent — hyphenated doc labels were already being stripped in precisely the same way; LEND_ACC_01 died only because the matcher did not recognise underscores. The reviewer's objection is a critique of the pre-existing policy, not of this diff.

Flipping a deliberately-tested, security-relevant gate is not something I should do unilaterally on one reviewer's read, so I have left the policy alone, kept this PR to the consistency fix, and am recording the disagreement instead of resolving it by fiat.

The decision that needs an owner

Given a lens that cites identifiers present in its supplied pinned-reference documents but absent from any structured catalogue, should the gate:

  1. strip them with a warning (today's behaviour, which this PR makes consistent) — provenance stays verifiable-only, at the cost of discarding a mapping the prompt asked for; or
  2. keep them and mark them unverifiable (the reviewer's proposal) — preserves the mapping, but reference_expectations then contains unauthorised content; or
  3. supply a real catalogue for these runs, so the question does not arise — arguably the actual fix, since the prompt and the gate currently disagree about what "supplied" means.

Option 3 looks right to me and is out of scope here. Until it is decided, this stays a draft.

Also from review

  • Taken: the matcher was a point fix for a class bug — LEND-ACC-01 and CRYTIC-ERC4626-05 still failed. Now matches -/_ segments in any combination with a numeric suffix; verified against 22 cases including the namespaced forms that must keep failing.
  • Rejected: stripping per-identifier instead of per-property. That breaks without weakening benchmark authority, which asserts a lens mixing a label with a forged namespaced citation is left byte-unchanged — the artifact is the evidence the authority check exists to surface. The all-or-nothing rule now carries a comment saying so, plus a test pinning the preservation.
  • Reviewer 1 confirmed no authorized identifier shape is endangered: real ones are colon-namespaced and never match the widened pattern.
  • Reviewer 2 confirmed all twelve of R44's identifiers match, so this would unblock that node — and that the matcher is linear (no ReDoS).

39/39 tests in artifact-gates.test.ts pass at 8cc40ae.

aviggiano and others added 2 commits August 6, 2026 15:34
`isUnsupportedExternalReferenceLabel` only allowed a hyphen before the digits, so
an underscore-separated catalog identifier was not recognized as an unsupported
external label. The sanitizer therefore left it in place and
`verifyPropertyProvenanceArtifacts` reported it as an unauthorized expectation,
failing the node.

R44's `property-specification-0kn0t` died on `LEND_ACC_01` this way, while
`LEND-01` would have been stripped silently in the same position — so whether a
lens node survived turned on the model's choice of punctuation rather than on
anything meaningful. Because `property-specification-fanin` depends on all eight
lenses, one such node blocks fan-in, invariant setup, Recon, findings and the
report.

Allow `_` as well as `-` as the separator. This only affects identifiers that are
already absent from every supplied catalog, so no authorized expectation changes
behaviour.

Regression asserts an underscore-separated unauthorized ID is sanitized alongside
a hyphenated one, that no unauthorized diagnostic is raised, and that the node
does not fail.

Fixes #283

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review found the first version was a point fix for a class bug: only `_` was
widened, so hyphen-segmented labels still failed the node. Measured through the
gate, `LEND-ACC-01` and `CRYTIC-ERC4626-05` both died as unauthorized while
`LEND_ACC_01` was stripped — and those multi-segment forms are common in the
catalogues these lens prompts cite, so the next label would have reproduced #283
under a new number.

Allow segments separated by `-` or `_` in any combination with a numeric suffix.
Verified against 22 cases: matches `LEND_ACC_01`, `LEND-ACC-01`,
`CRYTIC-ERC4626-05`, `ERC4626-999`, `LEND-01`, `A-1`; rejects `lend_acc_01`,
`_LEAD-1`, `A-`, `A-1x`, `LEND01`, `FOO.01`, `4626-01`, `LEND--1`, and the
namespaced `benchmark:unexpected` / `scfuzzbench:aave-v4:iSpoke_supply` forms that
must keep failing.

Review also suggested stripping per identifier so one unrecognised sibling would
not poison the rest of the property. Not taken: `"property lens gate strips
unsupported external labels without weakening benchmark authority"` already pins
the opposite, asserting that a lens mixing a label with a forged namespaced
citation is left byte-unchanged and unsanitized. That is deliberate — the artifact
as the model wrote it is the evidence the authority check exists to surface, and
sanitizing around the forgery would destroy it. The all-or-nothing rule now says
so in a comment, and a new test pins the preservation so the next reader does not
mistake it for the same oversight.

Refs #283

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@aviggiano
aviggiano marked this pull request as ready for review August 6, 2026 15:34
@aviggiano
aviggiano force-pushed the fix/lens-underscore-reference-labels branch from 8cc40ae to b393758 Compare August 6, 2026 15:34
@aviggiano

Copy link
Copy Markdown
Collaborator Author

Merging with launch / collect not green — GitHub Actions infrastructure, stated plainly

For the record rather than glossed over: launch and collect never ran. Both jobs' only step is Set up job, and that is what failed, before checkout and before any code in this branch executes:

Failed to resolve action download info. Error: Internal Server Error
Failed to resolve action download info. Error: Service Unavailable
##[error]Failed to resolve action download info.

Verified via gh api .../actions/jobs/<id> — steps[] contains exactly one entry, Set up job, conclusion: failure. I re-ran them three times across ~45 minutes; the action-resolution service stayed unavailable and one attempt ended cancelled mid-outage.

main carries no branch protection, so there are no required contexts; mergeStateStatus: UNSTABLE reflects only these two jobs.

Everything substantive is green:

  • release-gates ×2 — the build/test/lint gate
  • Greptile Review, semgrep, Socket Security ×2
  • locally: 39/39 in artifact-gates.test.ts, 280 pass / 0 fail in the full runtime suite, typecheck / eslint / prettier / git diff --check clean
  • two independent review rounds, every finding either applied or rejected with reasoning recorded above

I held #279 and #282 to a fully CLEAN status before merging. This one is a deliberate exception because the two outstanding jobs cannot start for reasons outside the repository, and they are not required. If someone wants them green before this lands, revert and re-run once GitHub recovers.

@aviggiano
aviggiano merged commit fdce587 into main Aug 6, 2026
9 of 15 checks passed
@aviggiano
aviggiano deleted the fix/lens-underscore-reference-labels branch August 6, 2026 16:29
aviggiano added a commit that referenced this pull request Aug 7, 2026
Integrates origin/main (fa8be64): PR #287's invariant-suite handoff deletion
channel and durable provenance, plus #282, #284, #290, #294, #296, #298, #300,
#303, #305 and #308. CHANGELOG keeps both Unreleased sections; the modal runner
test import list keeps both new symbols.

Fixes the review finding on #165 (issue #212). Compiled task specs can only name
singular `test/...` output roots, because they are built before the worktree
exists. Targets are not consistent: the Aave v4 target at 6959e321 has no `test/`
at all, only plural `tests/`. The generated workflow created the singular tree
regardless, so such a repository grew empty `test/recon`, `test/chimera` and
`test/invariants` directories, which setup.md tells the model to read as the
repository's convention, and retry cleanup then scrubbed that invented tree while
leaving the real `tests/` output behind.

Workspace output roots are now re-anchored at execution time onto the test root
the repository actually uses, restoring the semantics of main's `invariantTestRoots`
helper that this branch dropped in merge 2bbecba, at the layer this branch's retry
preparation uses.

Three review findings on the first cut of that fix are addressed here:

- Exactly ONE root is selected, never the union of those present. Anchoring under
  every existing root would have recreated the same defect from the other side: once
  an ancestor stage introduces the other root, each invariant stage would create an
  empty duplicate tree beside the real one. Preference follows
  INVARIANT_TEST_ROOT_NAMES, so a repository that has `test/` behaves exactly as it
  did before this helper existed and only a repository without it is redirected.
- The resolution is pinned per attempt at preparation time. Rediscovering per call was
  unsafe: prepareTaskWorkspaceOutputRoots finishes by replaying ancestor workspace
  patches, and an ancestor stage can introduce the other test root as a side effect, so
  a second independent discovery could select a root whose subdirectories were never
  created. taskWorkspaceOutputRoots would then lstat a missing path and throw a bare
  ENOENT instead of the typed artifact-contract failure, killing a retries:0 node on
  exactly the restart and resume paths this machinery exists to survive.
- Only ENOENT and ENOTDIR mean "this root is absent". Swallowing EACCES or ELOOP would
  silently fall back to the singular default against a root that does exist.

Test changes, also from review:

- The non-directory case was vacuous: with only a regular file named `test` present,
  "rejected it" and "fell back to the default" both read `test`. It now runs against a
  repository that also has a real `tests/`, so the outputs differ; verified by mutation
  (dropping the isDirectory() guard fails the test).
- taskWorkspaceOutputRoots' use of the helper is now pinned in the generated-source
  assertions. Reverting only that call site previously left every test green while
  every task on a `tests/`-only target died on a bare ENOENT.
- Adds coverage for the pinning behaviour, for per-attempt isolation of the pin, and
  for surfacing an unreadable candidate root.

Also repairs a merge-integration gap the new #217 harness could not have anticipated:
it lifts resetTaskArtifactsForRetry, which on this branch calls prepareAnchoredDirectory
(branch-only, absent from main), so the harness now supplies that collaborator and drops
the dead invariantTestRoots stub.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Lens reference-expectation sanitizer only recognizes hyphenated labels, so underscore catalog IDs fail the node

1 participant