docs(#2665): catalog the config-location and live-config-guard seams in CONTEXT.md

Round 2, Minor. "Workspace seams" carried WORKTREE.SEAM.* and
CONFIG.SEAM.loadConfig-context but nothing for this PR's mechanism or its env
vars, so the one machine-readable place a future author would look said nothing
about the class that has now recurred three times.

Ten predicates across two groups:

  CONFIG.LOCATION.SEAM.*  — the scrub set's four derivation sources and the rule
                            that a new var is made ENUMERABLE rather than
                            appended; the two-families distinction (runtime
                            configHomes vs GSD's own GSD_HOME/GSD_AGENTS_DIR)
                            that round 2 turned on; kimi's two config-location
                            vars; and the in-process scrub requirement, since
                            HOME sandboxing alone is the trap that produced
                            Blocker 1 twice.
  LIVE-CONFIG.GUARD.SEAM.* — module + exports + why it is scripts/ and not
                            scripts/lib/; ownership-based scope; the two
                            non-root targets and their asymmetric treatment;
                            the truncation contract; the report-not-fatal
                            severity ratchet; and that CI is structurally blind
                            here, so green CI is not evidence.

Both generated indexes regenerated. docs/CONTEXT-INDEX.json is checked by
lint:generated-sync (`gen-context-index.cjs --check`) LINE-NUMBER-SENSITIVELY,
and the example's own committed index is separately checked by
lint-example-parser-parity.cjs, which the first regen did not satisfy — editing
CONTEXT.md requires both, and only one of them says so in its error text.

Verified: parity lint rc=0, gen-context-index --check rc=0, full
lint:generated-sync rc=0. Regen diff audited — 10 predicates added, 0 removed,
0 values changed; the example index's remaining churn is line-number re-baking,
which is exactly why the parity lint excludes line numbers.
This commit is contained in:
0xdhx
2026-08-01 02:05:12 -05:00
parent fec42e9a7d
commit 434d71b03b
3 changed files with 545 additions and 434 deletions

View File

@@ -588,6 +588,16 @@ The prompt-level data/instruction isolation seam for untrusted web/document ingr
`WORKSTREAM.NAME.POLICY.cjs-module=gsd-core/bin/lib/workstream-name-policy.cjs owns toWorkstreamSlug + active-name/path-segment validation`
`WORKSTREAM.POINTER.SEAM.cjs-module=gsd-core/bin/lib/active-workstream-store.cjs owns read/write self-heal for .planning/active-workstream`
`CONFIG.SEAM.loadConfig-context=loadConfig(cwd,{workstream}) replaces env-mutation fallback; no temporary process.env GSD_WORKSTREAM rewrites`
`CONFIG.LOCATION.SEAM.scrub-set=tests/helpers.cjs CONFIG_LOCATION_ENV_KEYS is DERIVED from four sources, never hand-listed: capability-registry runtimes[].runtime.configHome.env + runtime-homes NON_REGISTRY_CONFIG_HOME_DESCRIPTORS[].env + runtime-homes GSD_LOCATION_ENV_KEYS + a residue list (GROK_AGENTS_HOME, GSD_RUNTIME, GSD_PROJECT, GSD_WORKSTREAM); adding a config-location var means making it ENUMERABLE at one of those sources, not appending a literal`
`CONFIG.LOCATION.SEAM.two-families=runtime configHomes (where a third-party runtime keeps config, registry- or descriptor-declared) and GSD's OWN location vars (GSD_HOME -> $GSD_HOME/.gsd store, GSD_AGENTS_DIR -> getAgentsDir priority 1) are DISTINCT families; no registry derivation reaches the second, and treating a miss there as a registry gap is what produced review round 2`
`CONFIG.LOCATION.SEAM.kimi-two-homes=kimi declares TWO config-location vars: KIMI_CONFIG_DIR (registry, generic Agent-Skills root via resolveKimiGlobalDir) and KIMI_SHARE_DIR (KIMI_HOOKS_TOML_DESCRIPTOR, kimi's OWN native config.toml carrying GSD's [[hooks]] block via resolveKimiHooksTomlDir); a registry-only derivation covers the first and silently misses the second`
`CONFIG.LOCATION.SEAM.in-process-scrub=TEST_ENV_BASE reaches CHILD env only; a test calling install() IN-PROCESS must additionally use helpers.scrubConfigLocationEnv() in beforeEach + its restorer in afterEach — HOME/USERPROFILE sandboxing is NOT sufficient because getGlobalConfigDir is env-FIRST`
`LIVE-CONFIG.GUARD.SEAM.module=scripts/live-config-guard.cjs (deliberately NOT scripts/lib/, which the installer copies to users wholesale while uninstall removes only an allowlist); exports [resolveLiveConfigRoots, resolveExtraWatchTargets, snapshotLiveConfig, diffLiveConfig, formatViolations, newestMtime]; driven by scripts/run-tests.cjs pre/post suite`
`LIVE-CONFIG.GUARD.SEAM.scope=ownership-based, never whole-root: GSD_OWNED_ENTRIES top-level footprint + gsd--prefixed children of GSD_PREFIXED_PARENTS (dirs shared with the host agent); watching a shared root wholesale false-positives on the host's own writes and a guard that cries wolf gets disabled`
`LIVE-CONFIG.GUARD.SEAM.non-root-targets=resolveExtraWatchTargets covers the two live write surfaces that are not runtime config ROOTS: $GSD_HOME/.gsd watched WHOLESALE (exclusively GSD-owned, so the shared-root trap does not apply) and <resolveKimiHooksTomlDir()>/config.toml watched as a SINGLE FILE (the root belongs to Kimi CLI); passed to snapshotLiveConfig explicitly so a fixture-root caller cannot pull the real ~/.gsd into its snapshot`
`LIVE-CONFIG.GUARD.SEAM.truncation=MAX_ENTRIES/MAX_DEPTH bound the walk; a bound hit sets truncated and diffLiveConfig emits kind:'unverified' — a truncated scan MUST NOT read as clean; boundary covered at {limit-1,limit,limit+1} via newestMtime's injected budget plus fast-check monotonicity, per RULESET.TESTS.boundary-coverage + RULESET.TESTS.property-based-testing`
`LIVE-CONFIG.GUARD.SEAM.severity=reports by default; fails only under GSD_STRICT_LIVE_CONFIG_GUARD=1, skipped by GSD_SKIP_LIVE_CONFIG_GUARD=1; promote to fatal once the USERPROFILE sweep lands, mirroring local/no-source-grep warn->error (ADR 452)`
`LIVE-CONFIG.GUARD.SEAM.ci-blind=CI cannot catch the ambient-env half of this class at all — CI never has these vars set, so green CI is not evidence; the guard is the only loud signal`
---

View File

@@ -1,14 +1,15 @@
{
"schemaVersion": 1,
"count": 416,
"count": 425,
"classes": {
"ARCH": 1,
"CI": 2,
"CONFIG": 1,
"DEFECT": 168,
"CONFIG": 5,
"DEFECT": 167,
"EXEC": 8,
"GSD-RESEARCH": 6,
"LEARNING": 1,
"LIVE-CONFIG": 6,
"META": 4,
"PLANNING": 3,
"PR": 2,
@@ -39,6 +40,26 @@
"klass": "CI",
"value": "hard-fail if PR body lacks closes/fixes/resolves #<issue>"
},
{
"id": "CONFIG.LOCATION.SEAM.in-process-scrub",
"klass": "CONFIG",
"value": "TEST_ENV_BASE reaches CHILD env only; a test calling install() IN-PROCESS must additionally use helpers.scrubConfigLocationEnv() in beforeEach + its restorer in afterEach — HOME/USERPROFILE sandboxing is NOT sufficient because getGlobalConfigDir is env-FIRST"
},
{
"id": "CONFIG.LOCATION.SEAM.kimi-two-homes",
"klass": "CONFIG",
"value": "kimi declares TWO config-location vars: KIMI_CONFIG_DIR (registry, generic Agent-Skills root via resolveKimiGlobalDir) and KIMI_SHARE_DIR (KIMI_HOOKS_TOML_DESCRIPTOR, kimi's OWN native config.toml carrying GSD's [[hooks]] block via resolveKimiHooksTomlDir); a registry-only derivation covers the first and silently misses the second"
},
{
"id": "CONFIG.LOCATION.SEAM.scrub-set",
"klass": "CONFIG",
"value": "tests/helpers.cjs CONFIG_LOCATION_ENV_KEYS is DERIVED from four sources, never hand-listed: capability-registry runtimes[].runtime.configHome.env + runtime-homes NON_REGISTRY_CONFIG_HOME_DESCRIPTORS[].env + runtime-homes GSD_LOCATION_ENV_KEYS + a residue list (GROK_AGENTS_HOME, GSD_RUNTIME, GSD_PROJECT, GSD_WORKSTREAM); adding a config-location var means making it ENUMERABLE at one of those sources, not appending a literal"
},
{
"id": "CONFIG.LOCATION.SEAM.two-families",
"klass": "CONFIG",
"value": "runtime configHomes (where a third-party runtime keeps config, registry- or descriptor-declared) and GSD's OWN location vars (GSD_HOME -> $GSD_HOME/.gsd store, GSD_AGENTS_DIR -> getAgentsDir priority 1) are DISTINCT families; no registry derivation reaches the second, and treating a miss there as a registry gap is what produced review round 2"
},
{
"id": "CONFIG.SEAM.loadConfig-context",
"klass": "CONFIG",
@@ -334,11 +355,6 @@
"klass": "DEFECT",
"value": "security_reminder_hook can block Write on substring match (e.g. a literal child-process call-expression token); workaround is heredoc to /tmp then mv into place, or use Edit instead — Edit hooks are more lenient than Write hooks"
},
{
"id": "DEFECT.HOST-RESERVED-DIR-NAME",
"klass": "DEFECT",
"value": "a host runtime reserves a directory NAME that GSD also writes verbatim, so the mere presence of GSD's directory trips the host's own reserved-name detection regardless of contents; example: pi (#3023) treats GSD's shared-hooks bundle dir hooks/ as its own deprecated extension location and printed a startup warning purely because checkDeprecatedExtensionDirs() in packages/coding-agent/src/migrations.ts gates on a bare existsSync(hooksDir) with no readdir/emptiness check (unlike its tools/ sibling); fix-forward=make the shared-hooks directory name descriptor-driven (hostBehaviors.sharedHooksDirName, default hooks) and override it per-runtime when a name collision is detected (pi sets gsd-hooks), with adapters probing the new name then falling back to the legacy name for dev/half-upgraded trees"
},
{
"id": "DEFECT.INVENTORY-DRIFT.detect",
"klass": "DEFECT",
@@ -959,6 +975,36 @@
"klass": "LEARNING",
"value": "PR #3708 commit 2df566ed reserved NOTE_RESERVE_TOKENS in pressure-threshold AND in minSet pre-check; both buggy paths only fire when baseTokens ∈ (effectiveBudget - NOTE_RESERVE_TOKENS, effectiveBudget]; original test suite used budgets far from that band so neither path was exercised; fix bde1ae8f confines NOTE_RESERVE accounting to post-trim assembly path only; future budget/limit code MUST add boundary fixtures per RULESET.TESTS.boundary-coverage.fixtures"
},
{
"id": "LIVE-CONFIG.GUARD.SEAM.ci-blind",
"klass": "LIVE-CONFIG",
"value": "CI cannot catch the ambient-env half of this class at all — CI never has these vars set, so green CI is not evidence; the guard is the only loud signal"
},
{
"id": "LIVE-CONFIG.GUARD.SEAM.module",
"klass": "LIVE-CONFIG",
"value": "scripts/live-config-guard.cjs (deliberately NOT scripts/lib/, which the installer copies to users wholesale while uninstall removes only an allowlist); exports [resolveLiveConfigRoots, resolveExtraWatchTargets, snapshotLiveConfig, diffLiveConfig, formatViolations, newestMtime]; driven by scripts/run-tests.cjs pre/post suite"
},
{
"id": "LIVE-CONFIG.GUARD.SEAM.non-root-targets",
"klass": "LIVE-CONFIG",
"value": "resolveExtraWatchTargets covers the two live write surfaces that are not runtime config ROOTS: $GSD_HOME/.gsd watched WHOLESALE (exclusively GSD-owned, so the shared-root trap does not apply) and <resolveKimiHooksTomlDir()>/config.toml watched as a SINGLE FILE (the root belongs to Kimi CLI); passed to snapshotLiveConfig explicitly so a fixture-root caller cannot pull the real ~/.gsd into its snapshot"
},
{
"id": "LIVE-CONFIG.GUARD.SEAM.scope",
"klass": "LIVE-CONFIG",
"value": "ownership-based, never whole-root: GSD_OWNED_ENTRIES top-level footprint + gsd--prefixed children of GSD_PREFIXED_PARENTS (dirs shared with the host agent); watching a shared root wholesale false-positives on the host's own writes and a guard that cries wolf gets disabled"
},
{
"id": "LIVE-CONFIG.GUARD.SEAM.severity",
"klass": "LIVE-CONFIG",
"value": "reports by default; fails only under GSD_STRICT_LIVE_CONFIG_GUARD=1, skipped by GSD_SKIP_LIVE_CONFIG_GUARD=1; promote to fatal once the USERPROFILE sweep lands, mirroring local/no-source-grep warn->error (ADR 452)"
},
{
"id": "LIVE-CONFIG.GUARD.SEAM.truncation",
"klass": "LIVE-CONFIG",
"value": "MAX_ENTRIES/MAX_DEPTH bound the walk; a bound hit sets truncated and diffLiveConfig emits kind:'unverified' — a truncated scan MUST NOT read as clean; boundary covered at {limit-1,limit,limit+1} via newestMtime's injected budget plus fast-check monotonicity, per RULESET.TESTS.boundary-coverage + RULESET.TESTS.property-based-testing"
},
{
"id": "META.RULE.brief-must-cite-doc",
"klass": "META",

File diff suppressed because one or more lines are too long