feat(research): tiered allowlist resolver — S1 resolver port + Tier 2 - #850
Merged
Conversation
Tier 0 (builtin 7 keys, zero behavior change) + Tier 2 (consumer-acked .ai-factory/research-allowlist.json, cargo-vet-shaped, fail-closed) behind a new resolver port with an ecosystem-adapter seam (Tier-1 npm derivation = S2; non-JS toolchains = S4 behind the same interface). Cross-tier invariants per kickoff §4: https-only; IP-literal reject (bare IPv4 + bracketed IPv6); host canonicalization; xn-- reject outside an explicit Tier-2 ack; subdomain- inclusive exact-host matching (no eTLD+1/PSL — kickoff §8 DN #3 resolution). One-arg validateProvenance stays Tier-0-only with byte-identical reason strings (kickoff §5 S1; DN #7 Option A). All 5 S1 paired negatives observed RED before their fix (TDD); research+synthesizer suites untouched-green. Kickoff: .claude/orchestrator-prompts/rule-research-trust-tiers/kickoff.md §4-§5 (branch docs/kickoff-rule-research-trust-tiers). Prior-art: prior-art-evaluations.md#186 (cargo-vet audits.toml, ADAPT — committed human-acked trust records map onto .ai-factory/research-allowlist.json; parser BUILD, Rust-domain tool). Prior-art: prior-art-evaluations.md#187 (TUF targets-delegation, ADOPT VOCABULARY — terminating delegation ≙ explicit subdomain restricts; no runtime dependency).
This was referenced Jul 2, 2026
Merged
artyhoo
added a commit
that referenced
this pull request
Jul 2, 2026
…pl-plan; wire by-reference (#855) Lands the validator-chain design docs on staging (single-concern, docs-only): - docs/superpowers/specs/2026-07-02-diagnostics-core-design.md (D1: unified Diagnostic model + append-only FF code registry) + Status line recording S1+S2 merged (#850/#852), precondition p.1 satisfied, D1 unblocked. - docs/meta-factory/research-patches/2026-07-02-multi-toolchain-generalization.md + a new §10 with probe results P1-P3 (executed read-only this session, each adversarially verified; P2 Python native-drop = 90.9%/100% after the R7 correction, NO-GO for native-ruff default). - docs/superpowers/plans/2026-07-02-diagnostics-core-impl.md (new) — D1 implementation plan on a re-verified post-S3 code baseline (validateProvenance is 3-arg; DN #3 exact-host, no PSL; FF2xxx = 15 concrete reason classes; principle-09 sentinel range [20,85] count 85). - docs/meta-factory/EXECUTION-PLAN.md — one by-reference block wiring the chain (S1->S2->S3 #850/#852/#853 -> D1 -> B -> MT) under Phase 9+. Chain status verified against origin/staging @ 7507306: S3 = #853 MERGED; #811/#812 CLOSED. No capability commit (no packages/ files) -> no Prior-art trailer required.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
feat(research): tiered allowlist resolver — S1 resolver port + Tier 2
Stage S1 of the rule-research-trust-tiers umbrella (kickoff:
.claude/orchestrator-prompts/rule-research-trust-tiers/kickoff.md, branchdocs/kickoff-rule-research-trust-tiers; plan:docs/superpowers/plans/2026-07-02-rule-research-trust-tiers-impl.md).What ships
packages/core/research/allowlist-resolver.ts— resolver port: host invariant helpers (canonicalizeHost/isIpLiteral/hasPunycodeLabel/hostMatches), Tier-2 ack-file parser (loadAckFile, fail-closedAckFileError),resolveAllowedSources(ctx?)with the ecosystem-adapter seam (Tier-1 npm derivation = S2; non-JS toolchains = S4 behind the same interface), two-argvalidateProvenance(p, resolved, opts?)with tier order 0 → 1 → 2.packages/core/research/research-allowlist.schema.json— Tier-2 ack-file schema (cargo-vet-shaped:{key, hosts[], scope?, reason, ackedBy, ackedAt}), compiled ininternal-validators.ts(validateAckFileShape, same Ajv instance as the plan schema).packages/core/research/allowlist.ts— one-argvalidateProvenancedelegates to the resolver Tier-0-only (lazy init breaks the import cycle; no ctx ⇒ zero fs access). Reason strings byte-identical for the legacy corpus.docs/meta-factory/prior-art-evaluations.md; squash commit carries bothPrior-art:trailers.DN resolutions honored (kickoff §8, all resolved 2026-07-02)
DN #2 (ack activation = human-merged PR;
ackedBy= human), DN #3 (exact-host, no eTLD+1/PSL anywhere), DN #7 Option A (one-arg wrapper stays Tier-0-only; ctx threading = S2 Task 2.6).TDD evidence (each negative RED before its fix — kickoff AC 1)
loadAckFileabsent: positive controls fail (4 negatives passed vacuously viatoThrow(undefined)— why positive controls are load-bearing)2026-13-45: passes schema regex, no date guard → REDDate.parseguard → 8/8xn--) → 15/15allowlist.test.tsuntouched, research+synthesizer 146/146 greenFull runs:
packages/core/research/61/61 · research+synthesizer 146/146 ·test:principles267 passed / 1 skipped ·tsc --noEmitclean. ESLint could not run in the authoring sandbox (flat-config mismatch) — runnpx eslintlocally before push.§1.7 Forward-check applied
Complies with
no-paid-llm-in-ci.md— the resolver is deterministic with zero API calls (packages/core/research/allowlist-resolver.ts:157two-argvalidateProvenance, no network;allowlist-resolver.ts:58loadAckFilereads local fs only).build-first-reuse-default.md— SSOT consult in kickoff §3; ADAPT cargo-vet #186 with BUILD limited to schema+parser (allowlist-resolver.ts:58+research-allowlist.schema.json) per T16 rationale; ADOPT VOCABULARY TUF #187.dual-implementation-discipline.md§2(iv) — TS package capability, no hook/agent channel, so no dual-pair marker required. English-only internals (verified by principle 22). Capability-commit gate —packages/core/research/allowlist-resolver.ts:1is a new ≥80-LOC file underpackages/, so bothPrior-art:trailers (#186, #187) are present in the commit.§1.7 Backward-check applied
Implements kickoff §5 S1 + §4 cross-tier invariants: https-only (
packages/core/research/allowlist-resolver.ts:236), IP-literal reject incl. bracketed IPv6 (allowlist-resolver.ts:21), host canonicalization (allowlist-resolver.ts:15), xn-- rejected outside an explicit Tier-2 ack (allowlist-resolver.ts:29+ carve-outallowlist-resolver.ts:211), subdomain-inclusive exact-host match (allowlist-resolver.ts:34). Enforces the DN #2/#3/#7 resolutions recorded in kickoff §8. AC 4 evidenced by untouched-greenpackages/core/research/allowlist.test.ts. Supersedes nothing —packages/core/research/allowlist.ts:20demotes to Tier-0 data andallowlist.ts:42becomes a back-compat wrapper. Tier-0 preserves the pre-refactorok-verdict + reason strings for every curated-store input; it is NOT byte-identical on all inputs — the §4 cross-tier invariants (canonicalization, IP-literal + punycode rejection) apply to Tier-0, so three edge inputs diverge (trailing-dot FQDN of an allowed host →ok:true; IP/punycode → specific reason). These are pinned as a tested invariant inpackages/core/research/allowlist-resolver.test.ts(see Review round 1 below), not claimed as "zero change".Checklist
npx eslint packages/core/research/clean locallynpx vitest run packages/core/research/ packages/core/synthesizer/green locallynpm --prefix packages/core run test:principlesgreen locallyPrior-art:trailers (pre-push gate)Post-authoring delta (host session)
packages/core/install/synth-and-wire.bundle.mjsregenerated (scripts/build-synth-bundle.sh):allowlist.tsis a transitive input of the bundle, so the S1 edit triggered the known.ts→.mjsshipped-file cascade;--checknow green.templates/*/eslint.config.mjs) — there is no rooteslint.config.*, sonpx eslint packages/core/research/cannot run by construction. Gates actually run: vitest research+synthesizer 146/146,test:principles268/268,tsc --noEmitclean, pre-push full gate on push.Review round 1 — two adversarial reviewers (addressed)
Two read-only reviewers ran on the S1 diff: A = architecture/design/goal-fit; B = implementation/security (reviewed as an auth boundary, all vectors verified live with
node/vitest/tsc).ok:false→ok:true; IP-literal + punycode reason strings differ), and the regression suite structurally could not observe them — the project's own#trap-stated-but-not-enforced. The code is design-correct (kickoff §4 lists these as cross-tier invariants that apply to Tier-0). Fix: corrected the resolver docstring + pinned all three divergences as a tested invariant (allowlist-resolver.test.ts— newit('Tier-0 §4 cross-tier divergences … are pinned')). Executable truth replaces the prose overclaim.(p as Provenance & { packageName?: string })cast atallowlist-resolver.ts:177reads an S2-only field, so the Tier-1 block is dormant in S1 (verified:tier1Forunconditionally misses). Accepted forward-seam scaffolding;tscclean.hostMatchessegment-safe (evilnextjs.org/react.dev.evil.comrejected); IP normalization catches decimal/hex/octal/short forms (all →127.0.0.1→ rejected); userinfo (react.dev@evil.com→evil.com, rejected) no bypass; punycode carve-out correct;loadAckFilefail-closed on every structural case; import-cycle lazy-init order-safe; zeroany; 5 paired negatives non-vacuous.loadAckFiledoes not reject a bare public-suffix / single-label ack host (hosts:["com"]would authorize all of.com). Defense-in-depth only — gated behind the DN feat: self-application + meta-factory foundation (Phase 0.5–3) #2 human-merge trust boundary (an attacker cannot write the ack file), and DN chore(meta-factory): Phase 3 Step 0 retrofit + 3 MAJOR fixes #3 deliberately excludes PSL computation. Disposition: fold a single-label-host guard into S3 alongside the host-trust principle test, rather than deviate from the S1 loader plan (execute-prompt: plan deviation → stop). Also to reconcile in S3: the kickoff AC 4 / plan "zero behavior change" prose vs the now-tested Tier-0 divergences (the test is the authoritative artifact).