Description
Problem
Plain-object dictionary lookups that read through the prototype chain keep
recurring in the fork's core. Ten instances have been filed individually —
a lookup keyed by an external string (a plan name, effort name, route
name, header, table name) hits constructor / toString / __proto__
and returns an inherited value instead of missing. Fixes are landing
one at a time (a per-site Object.hasOwn guard, e.g. PR #794), and the
class keeps producing new instances faster than the drip closes it.
Known instances (verify current status at triage): #774, #791, #792,
#793, #804 (fixed), #805 (fixed), #819 (fixed), #824 (fix PR open),
#830, #831. #831 shows the sharpest symptom so far: a route named
'constructor' crashes the governance gate outright.
Proposal
Stop fixing these one by one:
- Sweep: grep-driven audit of packages/core (and other packages
with externally-keyed plain-object reads) for the pattern classes:
obj[key]/obj[key] ?? fallbackwhereobjis a plain object or
object literal used as a dictionary andkeyis externally supplied
(names from config, plans, providers, routes, efforts, headers).
File or fix each hit. - Shared guard: one blessed way to do dictionary reads — a helper
(own-property lookup) or a convention (Map / Object.create(null) /
Object.hasOwn) applied at the sweep sites, so the next author copies
the safe shape. - Regression net: a lint rule or a targeted test convention that
flags new bare dictionary reads on plain objects in core, so the
class stays closed.
Acceptance
- The audit's search patterns and hit list are recorded on this issue.
- Every confirmed hit is fixed or split into its own issue.
- The shared guard exists and the sweep sites use it.
- A lint/test mechanism (or a documented reason one is impractical) is
in place.
Filed by the operator after the tenth instance (#831) landed; the
individual fix issues remain the lanes' work — this issue owns the
class, not the instances.
Acceptance criteria
- A grep-driven sweep of packages/core (and any other package with externally-keyed plain-object reads) is recorded on this issue: every hit is fixed, filed as its own issue, or explicitly dispositioned as safe.
- A shared guard exists per the proposal, and swept sites use it where it fits instead of new per-site one-offs.
- Each known instance (fix(core): AISDKNative package tables read through the prototype chain — a package named "constructor" resolves to a function, not a package string #774, fix(core): ModelRouteSchedule day names read through the prototype chain — "constructor" and "__proto__" parse as valid days #791, fix(core): ShellParse ARITY table reads through the prototype chain — a "constructor" command head yields an empty permission prefix and a " *" saved pattern #792, fix(core): MessagingTemplate.constant reads config vars through the prototype chain — "{constructor}" resolves to a function and crashes rendering when any messaging.vars are configured #793, fix(core): QuotaBurnRate priors lookup reads through the prototype chain — an effort named 'constructor' returns a function as percentPerSession #830, fix(core): ModelGovernance route and variant lookups read through the prototype chain — a route or variant named 'constructor' crashes the governance gate #831; fix(core): ConfigVariable {env:} substitution reads through the prototype chain — "{env:constructor}" injects the Object function source and ":-" fallbacks are ignored for prototype names #804/fix(codemode): CodeMode fetch response headers read through the prototype chain — get("constructor") returns the Object function and has("__proto__") is true for headers the server never sent #805/fix(core,ai): ModelRouteLimits.windows crashes on rate-limit windows named after Object.prototype members — sibling limit/reset lookups read through the prototype chain #819 already fixed) is verified fixed/closed or linked to its fix.
- A regression test shows a
constructor/__proto__key missing cleanly instead of returning an inherited value.
Activity
developing-today commentedon Oct 29, 2026
OPENCODE-187
dezren39 commentedon Oct 29, 2026
agent-note:v1
state: filed
Agent: main (operator)
Found via: the tenth prototype-chain instance (#831) filed 2026-10-29;
operator filed this umbrella so the class gets closed, not just the
instances. Triage: verify each listed instance's status, pin the sweep
patterns, and route the guard + sweep as follow-up packets.
dezren39 commentedon Oct 29, 2026
agent-note:v1
state: picked-up
Agent: repo-worker-opencode (queue task 3141, triage)
Found via: queue claim repo-health-opencode-pilot
Next: verify instance statuses, pin sweep patterns + packages/core hit list on this issue, route guard/regression-net follow-ups. No hard-stop labels present.
dezren39 commentedon Oct 29, 2026
agent-note:v1
state: triaged
Agent: repo-worker-opencode (queue task 3141, triage)
Verified at: 2026-10-29, laptop primary main 71608cb (includes PR #843)
Instance status — all ten verified fresh (2026-10-29)
Class is still expanding beyond the original ten: #839 (codemode std.pick — also with a duplicate PR pair #859/#861) and #856 (PR #858) were filed after this umbrella.
Sweep patterns pinned (packages/core/src, grep)
grep -rnE '\b[A-Z][A-Z_0-9]{2,}\[[a-zA-Z_]' packages/core/src --include='*.ts'in-operator membership on plain objects (prototype-inclusive):grep -rnE ' in [a-zA-Z_.()]+' packages/core/src --include='*.ts'(high noise; only real for variable-length external keys)??fallback — prototype values are not nullish, so the fallback never fires (the most dangerous shape):grep -rnE '\[[a-zA-Z_.]+\] \?\?' packages/core/src --include='*.ts'Object.hasOwnuses in 49 files underpackages/, 34 inpackages/core/src;Object.create(null)appears nowhere in core; no shared helper exists.Hit list from this sweep
New confirmed hits — split into their own issues:
variant.ts:30PROTOCOLS[model.package]— plain Record, external package name, truthiness-only guard → split filed as fix(core): Variant PROTOCOLS table reads through the prototype chain — a model package named "constructor" resolves to the Object function #862.model-plan.ts:247store.pools[poolID] ?? {}— Schema.Record from the store file, external poolID → split filed as fix(core): ModelPlan setPool pools lookup reads through the prototype chain — a pool named "constructor" never takes the ?? {} fallback #863.wellknown.ts:88variables[name] ?? process.env[name] ?? ""— name from a remote manifest, same symptom as fix(core): ConfigVariable {env:} substitution reads through the prototype chain — "{env:constructor}" injects the Object function source and ":-" fallbacks are ignored for prototype names #804 in a different file → split filed as fix(core): WellKnown {env:} substitution reads through the prototype chain — "{env:constructor}" injects the Object function into remote-config URLs/headers #864.Already-tracked hits still unguarded on main (fixes in flight, listed above):
quota-snapshot.ts:84(#824),quota-burn-rate.ts:132-133(#830),session/model-governance.ts:200,219,234(#831).Candidates needing confirmation (routed to the guard/sweep follow-up, not split):
model-route-review.ts:238-252weights[candidate] ?? 1/excluded[reason]counters — weights come from persisted decision detail; a candidate/reason namedconstructorwould poison sums/counters.shell/select.ts:75META[name(file)]— Windows-only, basename key;constructorfile returns the Object function,denycheck reads undefined → benign but a prototype read.model-resolver.ts:326/effect/websocket-constructor.ts:20—process.env[...]/ environment-object reads; env-family, covered in principle by the fix(core): ConfigVariable {env:} substitution reads through the prototype chain — "{env:constructor}" injects the Object function source and ":-" fallbacks are ignored for prototype names #804 pattern.Examined and safe, with reason (do not re-file):
form.ts:479,482+model-route-limits.ts:66,69MONTH_NUMBERS — regex capture is constrained to(jan|feb|…)prefixes.model-route-limits.ts:100,133UNITS / RELATIVE_UNITS — key constrained by the amount regex(ms|s|m|h|d).session/runner/publish-llm-event.ts:674INPUT_FAILURES — key is the closed unionInputFailureReason, internal only.session/compaction.ts:444SHRINK_STEPS,model-route-schedule.ts:315DAY_LABELS,model-route.tsweights — numeric indexes.model-route-schedule.ts:171SHORT_DAYS — key fromIntlweekday formatting, trusted.ripgrep/binary.ts:97PLATFORM — key is${process.arch}-${process.platform}, internal.shell/scan.ts:1606BASH_ANSI_ESCAPES — single-char key (guarded byin; no single-char prototype member);scan.ts:2280POWERSHELL_ESCAPES — single-char key,?? charfallback sound for the same reason.database/v1-migration.bun.ts:924-928RENAMED_TOOLS — names derived fromObject.keys(RENAMED_TOOLS)(own keys only).Routing
Triage verdict: the class is real, per-site drip is working for filed instances (7/10 closed, 3/10 with PRs open), but the sweep found 3 new sites in one pass — the guard + net follow-up is the load-bearing item, not more instance fixes.
dezren39 commentedon Oct 29, 2026
agent-note:v1
state: done (triage)
Agent: repo-worker-opencode (queue task 3141)
Result: triage verdict + instance statuses + sweep patterns/hit list recorded in comment 6494535137 above. Splits filed: #862 (task 3393), #863 (task 3394), #864 (task 3396); guard + regression net: #865 (task 3397). This umbrella stays open — #865 owns closing the class. Noted for the #824 shepherd: PRs #825 and #835 are a duplicate pair.
dezren39 commentedon Oct 30, 2026
Intake review (fleet monitor, 2026-10-29 run):
Object.hasOwnguard.dezren39 commentedon Oct 30, 2026
agent-note:v1
state: done — closing umbrella
Agent: issue sweep (issue-sweep-rotating)
Session/ID: issue-sweep-rotating run 2026-10-29 20:24 CDT (correction pass)
Closing #833 — acceptance verified complete 2026-10-29
All four acceptance points are met, so this umbrella is complete. (Correction note: an earlier sweep entry this run briefly recorded #864/#865 as still open from a stale read; fresh gh api reads show both closed/completed — the backlog entry has been corrected.)