Skip to content

fix(research): extend single-label host reject to Tier-1 derivation (§1.7 backward-sweep follow-up to #857) - #860

Merged
artyhoo merged 1 commit into
stagingfrom
fix/trust-tiers-tier1-single-label
Jul 2, 2026
Merged

fix(research): extend single-label host reject to Tier-1 derivation (§1.7 backward-sweep follow-up to #857)#860
artyhoo merged 1 commit into
stagingfrom
fix/trust-tiers-tier1-single-label

Conversation

@artyhoo

@artyhoo artyhoo commented Jul 2, 2026

Copy link
Copy Markdown
Owner

What & why

Follow-up to the merged #857. #857's §1.7 backward-sweep (done properly after an operator challenge) surfaced a parallel gap — but the fix landed on the branch after #857 was already squash-merged, so it did not reach staging. This PR carries just that one delta.

The gap (still open in staging): the single-label-host reject #857 added to Tier-2 (loadAckFile) had an identical sibling hole in Tier-1 derivation. tier1For's candidateFields filter rejected IP-literal / punycode / multi-tenant hosts but not bare TLDs — so a direct dependency with homepage: "https://com" (or repository: "https://io") derives a trusted Tier-1 host com, scope-locked to that package, which hostMatches then treats as authorizing every *.com for that package's research.

Reachability: non-escalating — the package must be an installed direct dependency (npm already ran its lifecycle code, a strictly larger grant). But it is a real, avoidable widening in the same class as the IP-literal / punycode rejects sitting beside it — the exact inconsistency #857 closed on the Tier-2 surface. Proved live: homepage:"https://com" → derived host com.

Fix: if (!host.includes('.')) continue; in the candidateFields loop, right after the isIpLiteral reject — so Tier-1 package-metadata hosts and Tier-2 ack hosts reject bare TLDs consistently. Paired-negative tier1.test.ts S2-N8 (com homepage + io repository → tier1For yields no host). Bundle regenerated (tier1For is bundled) — drift check green. research-source-trust.md §5 item 4 already records the both-surfaces coverage (came in via #857 commit 1's doc; this PR's source change makes it true).

This is the recursive-self-application (invariant #2) the backward-check exists to force: apply a new guard's logic to its sibling artefacts, not only the surface the finding started on.

Verification (on the current staging base)

  • tier1.test.ts S2-N8 green (RED without the guard, proven by the live derivation of com/io); positive control still authorizes a legitimate two-label host
  • resolver + tier1 + principle 30: 42 passed
  • both single-label guards present in the resolver (Tier-2 loadAckFile + Tier-1 candidateFields)
  • build:synth-bundle:check — cherry-picked bundle matches a fresh build on the staging base
  • tsc --noEmit clean; markdownlint clean

§1.7 Forward-check applied

Existing disciplines checked: no-paid-llm-in-ci.md — the guard + S2-N8 are deterministic vitest, zero API-billed calls (allowlist-resolver.ts:200). doc-authority-hierarchy.md §2-§3research-source-trust.md's Class-A + Authoritative-for header untouched; principle 09 green. build-first-reuse-default.md — in-file guard + test, no new dependency/module; REFERENCE prior-art-evaluations.md#186-#188. ai-laziness-traps.md §2 T15 — recursive self-application: the guard is swept onto its Tier-1 sibling, not left one-surface.

§1.7 Backward-check applied

The sweep (this PR is itself the sweep result): #857's Tier-2 guard rejects single-label hosts, so I enumerated every host-derivation surface for the same class of gap: Tier-0 (ALLOWED_SOURCES curated const) — hand-curated, not human/attacker-writable, no single-label hosts, no gap; Tier-1 (tier1For candidateFields) — GAP FOUND (homepage:"https://com" → trusted com), closed here at allowlist-resolver.ts:200 + paired-negative tier1.test.ts:205 (S2-N8); Tier-2 (loadAckFile) — closed in #857. The positive control at tier1.test.ts:227 still authorizes a legitimate two-label host, and the shared-apex slice stays pinned by 30-research-source-trust.test.ts:139. No host-derivation surface is left inconsistent; §5 structure + Class-A header preserved; principle 09/30 green.

…§1.7 backward-sweep)

The §1.7 backward-sweep of this umbrella's Tier-2 guard (loadAckFile single-label
reject) found the SAME single-label gap on the parallel host-derivation surface:
tier1For's candidateFields filter rejected IP-literal / punycode / multi-tenant
hosts but NOT bare TLDs. Proved live: homepage "https://com" (or repository
"https://io") derives a Tier-1 host "com", scope-locked to that package, which
hostMatches would then treat as authorizing every *.com for that package's
research. Non-escalating (requires the package to be an installed direct dep —
npm already ran its lifecycle code, a strictly larger grant), but a real,
avoidable widening in the same class as the IP-literal/punycode rejects sitting
beside it — the exact inconsistency the Tier-2 guard closed.

Fix: add `if (!host.includes('.')) continue;` to the candidateFields loop, right
after the isIpLiteral reject — so Tier-1 package-metadata hosts and Tier-2 ack
hosts reject bare TLDs consistently. Paired-negative tier1.test.ts S2-N8
(homepage "com" AND repository "io" → tier1For yields no host; RED without the
guard per the live derivation proof, GREEN with it). Bundle regenerated (tier1For
is bundled) — drift check green. §5 item 4 records the both-surfaces coverage.

This is the recursive-self-application (invariant #2) the backward-check exists to
force: apply the new guard's logic to its sibling artefacts, not just the one the
finding started on.

§1.7: forward-check — complies with no-paid-llm-in-ci.md (guard + S2-N8 are deterministic vitest, zero API, allowlist-resolver.ts:200); doc-authority-hierarchy.md §2-§3 (research-source-trust.md header untouched, principle 09 green); build-first-reuse-default.md (in-file guard + test, no new dep; REFERENCE prior-art-evaluations.md#186-#188); ai-laziness-traps.md §2 T15 (recursive self-application — the guard is swept onto its Tier-1 sibling, not left one-surface). backward-check — the sweep itself: enumerated every host-derivation surface (Tier-0 curated const — not human/attacker-writable; Tier-1 candidateFields — GAP found + closed here; Tier-2 loadAckFile — closed in the prior commit), tier1.test.ts:205 positive control still authorizes a legitimate two-label host; no surface left inconsistent.

Prior-art: skipped — in-file guard + paired-negative test extending an existing capability (the Tier-1 resolver) onto its sibling surface; no new dependency or code-module.
@artyhoo
artyhoo enabled auto-merge (squash) July 2, 2026 22:44
@artyhoo
artyhoo merged commit e83ff4f into staging Jul 2, 2026
35 of 37 checks passed
artyhoo pushed a commit that referenced this pull request Jul 3, 2026
…SSOT #195)

Reconcile the parallel-evolution collision with staging (moved to c07563d during
the D1 run) and unblock the merge, per maintainer 'go' 2026-07-03:

1. Cosmetic conflict in allowlist-resolver.ts — #860 independently added the
   byte-identical bare-TLD Tier-1 guard D1 also has (WF restore cc90b3a + S2-N8);
   resolved keeping D1's Diagnostic|null-migrated version (guard identical).
2. #861 shipped agents/backward-sweep-auditor.md with no Prior-art trailer/SSOT ->
   staging audit-self was RED on principle 11 F1. Closed as an SSOT DATA fix: new
   entry #195 (BUILD verdict, cold PR-blind sibling-surface enumerator, no upstream
   analog) supplies the F1 match. No code change to #861's agent; the underlying gap
   is #861's, fixed here under explicit maintainer authorization to land the chain.

Bundle regenerated (--check green). Full packages/core suite 1893 passed | 12
skipped; principle 11 8/8; typecheck clean post-merge.

Prior-art: skipped — merge reconciliation + an SSOT data-entry (#195) closing an inherited pre-existing F1 gap; no new capability introduced by this merge
artyhoo added a commit that referenced this pull request Jul 3, 2026
…ging (#879)

Kickoff was authored locally on fix/trust-tiers-s5-readjudication (whose own
work is already merged via #857/#860) and never committed. Per
kickoff-staging-placement.md, a kickoff only on a feature branch is invisible
to /pipeline and aif dispatch — land it on staging before any dispatch.

Prior-art: skipped — docs-only kickoff authoring, no new capability

Co-authored-by: t <t@t.co>
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.

2 participants