Skip to content

feat(research): tiered allowlist resolver — S1 resolver port + Tier 2 - #850

Merged
artyhoo merged 1 commit into
stagingfrom
feat/trust-tiers-s1-resolver
Jul 2, 2026
Merged

feat(research): tiered allowlist resolver — S1 resolver port + Tier 2#850
artyhoo merged 1 commit into
stagingfrom
feat/trust-tiers-s1-resolver

Conversation

@artyhoo

@artyhoo artyhoo commented Jul 2, 2026

Copy link
Copy Markdown
Owner

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, branch docs/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-closed AckFileError), resolveAllowedSources(ctx?) with the ecosystem-adapter seam (Tier-1 npm derivation = S2; non-JS toolchains = S4 behind the same interface), two-arg validateProvenance(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 in internal-validators.ts (validateAckFileShape, same Ajv instance as the plan schema).
  • packages/core/research/allowlist.ts — one-arg validateProvenance delegates 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.
  • SSOT rows feat(n8-c3): /meta-orchestrator skill — BUILD verdict I-phase complete (Sub-wave D) #186 (cargo-vet, ADAPT) + chore: resync staging→main (promote 38 PRs + 3 merge-commits) #187 (TUF, ADOPT VOCABULARY) in docs/meta-factory/prior-art-evaluations.md; squash commit carries both Prior-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)

Round RED observed Fix
1.1 helpers suite fails to load (module absent) helpers implemented → 3/3
1.2 ack file loadAckFile absent: positive controls fail (4 negatives passed vacuously via toThrow(undefined) — why positive controls are load-bearing) schema + parser → 7/8
1.2 semantic S1-N3 2026-13-45: passes schema regex, no date guard → RED Date.parse guard → 8/8
1.3 resolver collection error (exports absent) resolver + validator → 14/15
1.3 semantic carve-out test: blanket xn-- reject in Tier-2 → RED explicit-ack check (acked host itself carries xn--) → 15/15
1.4 wrapper — (regression round) allowlist.test.ts untouched, research+synthesizer 146/146 green

Full runs: packages/core/research/ 61/61 · research+synthesizer 146/146 · test:principles 267 passed / 1 skipped · tsc --noEmit clean. ESLint could not run in the authoring sandbox (flat-config mismatch) — run npx eslint locally 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:157 two-arg validateProvenance, no network; allowlist-resolver.ts:58 loadAckFile reads 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:1 is a new ≥80-LOC file under packages/, so both Prior-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-out allowlist-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-green packages/core/research/allowlist.test.ts. Supersedes nothing — packages/core/research/allowlist.ts:20 demotes to Tier-0 data and allowlist.ts:42 becomes a back-compat wrapper. Tier-0 preserves the pre-refactor ok-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 in packages/core/research/allowlist-resolver.test.ts (see Review round 1 below), not claimed as "zero change".

Checklist

  • npx eslint packages/core/research/ clean locally
  • npx vitest run packages/core/research/ packages/core/synthesizer/ green locally
  • npm --prefix packages/core run test:principles green locally
  • Squash commit message carries both Prior-art: trailers (pre-push gate)

Post-authoring delta (host session)

  • packages/core/install/synth-and-wire.bundle.mjs regenerated (scripts/build-synth-bundle.sh): allowlist.ts is a transitive input of the bundle, so the S1 edit triggered the known .ts→.mjs shipped-file cascade; --check now green.
  • Repo-level ESLint run from the plan is not applicable: the repo ships ESLint configs as consumer templates only (templates/*/eslint.config.mjs) — there is no root eslint.config.*, so npx eslint packages/core/research/ cannot run by construction. Gates actually run: vitest research+synthesizer 146/146, test:principles 268/268, tsc --noEmit clean, 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).

  • A — MAJOR feat: audit fixes Phase 1+2+3 (R2/R11 drift, manifest SSOT, depcruise integration) #1 (FIXED): the "zero behavior change / byte-identical reason strings" claim for Tier-0 was falsifiable on three edge inputs (trailing-dot FQDN flips 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 — new it('Tier-0 §4 cross-tier divergences … are pinned')). Executable truth replaces the prose overclaim.
  • A — MINOR feat: self-application + meta-factory foundation (Phase 0.5–3) #2 (no action): the (p as Provenance & { packageName?: string }) cast at allowlist-resolver.ts:177 reads an S2-only field, so the Tier-1 block is dormant in S1 (verified: tier1For unconditionally misses). Accepted forward-seam scaffolding; tsc clean.
  • B — SHIP-READY. Hunt items A–I all clean, verified live: hostMatches segment-safe (evilnextjs.org/react.dev.evil.com rejected); IP normalization catches decimal/hex/octal/short forms (all → 127.0.0.1 → rejected); userinfo (react.dev@evil.comevil.com, rejected) no bypass; punycode carve-out correct; loadAckFile fail-closed on every structural case; import-cycle lazy-init order-safe; zero any; 5 paired negatives non-vacuous.
  • B — MINOR (deferred to S3, tracked): loadAckFile does 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).

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).
@artyhoo
artyhoo merged commit b8031c1 into staging Jul 2, 2026
36 of 37 checks passed
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.
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.

1 participant