instar 1.3.523 → 1.3.524

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  2. package/dist/config/ConfigDefaults.js +12 -0
  3. package/dist/config/ConfigDefaults.js.map +1 -1
  4. package/dist/core/CredentialIdentityOracle.d.ts +59 -0
  5. package/dist/core/CredentialIdentityOracle.d.ts.map +1 -0
  6. package/dist/core/CredentialIdentityOracle.js +89 -0
  7. package/dist/core/CredentialIdentityOracle.js.map +1 -0
  8. package/dist/core/CredentialLocationLedger.d.ts +187 -0
  9. package/dist/core/CredentialLocationLedger.d.ts.map +1 -0
  10. package/dist/core/CredentialLocationLedger.js +351 -0
  11. package/dist/core/CredentialLocationLedger.js.map +1 -0
  12. package/dist/core/CredentialWriteFunnel.d.ts +77 -0
  13. package/dist/core/CredentialWriteFunnel.d.ts.map +1 -0
  14. package/dist/core/CredentialWriteFunnel.js +128 -0
  15. package/dist/core/CredentialWriteFunnel.js.map +1 -0
  16. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  17. package/dist/core/devGatedFeatures.js +5 -0
  18. package/dist/core/devGatedFeatures.js.map +1 -1
  19. package/dist/core/types.d.ts +35 -0
  20. package/dist/core/types.d.ts.map +1 -1
  21. package/dist/core/types.js.map +1 -1
  22. package/package.json +1 -1
  23. package/scripts/lint-no-direct-llm-http.js +6 -0
  24. package/src/data/builtin-manifest.json +2 -2
  25. package/upgrades/1.3.524.md +31 -0
  26. package/upgrades/side-effects/live-credential-repointing-increment-a-foundation.md +75 -0
  27. package/upgrades/side-effects/live-credential-repointing-increment-a-funnel-primitive.md +66 -0
  28. package/upgrades/side-effects/live-credential-repointing-increment-a-ledger.md +71 -0
  29. package/upgrades/side-effects/live-credential-repointing-increment-a-oracle.md +68 -0
@@ -0,0 +1,66 @@
1
+ # Side-Effects Review — Live credential re-pointing (Increment A, Step 4a: CredentialWriteFunnel primitive)
2
+
3
+ **Version / slug:** `live-credential-repointing-increment-a-funnel-primitive`
4
+ **Date:** `2026-06-13`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required` (pure in-process lock primitive, no consumers, no writes — see §4)
7
+
8
+ ## Summary of the change
9
+
10
+ Adds `src/core/CredentialWriteFunnel.ts` (spec §2.2 "Concurrency model" / "Bounded under the lock") — the in-process serialization primitive for credential writes: `withSlotLock(slot, fn)` (per-slot lock), `withSlotLocks(slots, fn)` (canonical-ordered multi-slot, deadlock-free), and `withSingleMover(fn)` (machine-local single-mover mutex for swaps). Acquisition is try-lock-WITH-TIMEOUT — a slow holder degrades to a SKIPPED result with a named reason, never a wedged slot. Plus `tests/unit/credential-write-funnel.test.ts` (8 tests).
11
+
12
+ This is Step 4a: the PRIMITIVE only. It is **not yet wired** to any writer and the forbidding lint is **not yet added** — that is Step 4b (the lint can't land until every existing writer is routed through the funnel, or it breaks the build). So this commit changes no runtime behavior and writes no credentials.
13
+
14
+ ## Decision-point inventory
15
+
16
+ - `CredentialWriteFunnel.withSlotLock` / `withSlotLocks` / `withSingleMover` — **add** — serialize concurrent credential writes; on contention they SKIP (bounded), they never block indefinitely. No authority over agent behavior; pure concurrency control with no consumers yet.
17
+
18
+ ---
19
+
20
+ ## 1. Over-block
21
+
22
+ **No block/allow surface — over-block not applicable.** The only "refusal" is a try-lock timeout / single-mover-busy SKIP, which is the bounded-wait safety contract (§2.2): a write that can't get the lock in time is reported skipped so the caller degrades gracefully (e.g. the QuotaPoller refresh returns NO-SNAPSHOT) rather than wedging the slot. There are no consumers yet, so nothing is actually skipped in this commit.
23
+
24
+ ---
25
+
26
+ ## 2. Under-block
27
+
28
+ **No block/allow surface — under-block not applicable.** The funnel cannot serialize a writer that does not route through it — that is precisely what the Step-4b lint exists to prevent, and is called out as the next commit. Within this primitive, the timeout/no-deadlock-after-skip and throw-releases-lock paths are unit-tested.
29
+
30
+ ---
31
+
32
+ ## 3. Level-of-abstraction fit
33
+
34
+ Correct layer. A `src/core` concurrency primitive, self-contained (the existing `withLock` helpers are cross-PROCESS file locks; this is the IN-process per-slot serialization the spec calls for). It mirrors the SafeGitExecutor / SafeFsExecutor single-funnel precedent the spec names. It bounds only ACQUISITION; the caller bounds its own inner `await` (e.g. a refresh fetch carries its own `AbortSignal.timeout`) — documented in the module header.
35
+
36
+ ---
37
+
38
+ ## 4. Signal vs authority compliance
39
+
40
+ Compliant — it is mechanism, not authority. It holds no policy and gates no agent behavior; it serializes writes and reports contention as a bounded skip. It cannot become a "brittle check with blocking authority" because it makes no allow/deny decision about content — only about lock availability, with a deterministic bounded outcome. **Second-pass review: not required** under Phase 5 — no consumers, no writes, no messaging/dispatch/session decision. The point where the funnel gains real authority over credential writes is **Step 4b** (routing the four writers + the forbidding lint) and **Step 5** (the swap executor) — both will carry the second-pass review.
41
+
42
+ ---
43
+
44
+ ## 5. Interactions
45
+
46
+ - **No consumers yet** — nothing calls the funnel in this commit, so it cannot race, shadow, or double-fire against any existing path. Routing the four in-process writers (swap executor, QuotaPoller 401-refresh, OAuthRefresher/EnrollmentWizard, KeychainCredentialProvider.writeCredentials) is Step 4b.
47
+ - **All state in-memory** — a process restart clears any crash-stale lock/mutex state by construction (the spec's stated recovery for the single-mover).
48
+ - Lock order is documented (single-mover → slot locks ordered by path → ledger write) and `withSlotLocks` enforces the canonical order so two multi-slot ops can't deadlock on opposite orders (unit-tested).
49
+
50
+ ---
51
+
52
+ ## 6. External surfaces
53
+
54
+ None. No routes, no notices, no network, no filesystem, no keychain. Pure in-process promise/timer machinery. Nothing visible to other agents/users/systems.
55
+
56
+ ---
57
+
58
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
59
+
60
+ **Machine-local BY DESIGN** — credential writes happen against THIS machine's keychain, so the serialization that protects them is inherently per-process/per-machine. The single-mover mutex is explicitly "machine-local" (spec §2.2): it serializes swaps within one machine; cross-machine coordination of a topic move is the existing handoff guard's job (composed-with in Step 5), not this lock's. There is no shared state to replicate.
61
+
62
+ ---
63
+
64
+ ## 8. Rollback cost
65
+
66
+ Near-zero. New file + new test only; no consumers, no writes, no migration. Plain `git revert`. Because nothing uses the funnel yet, a revert leaves no behavior change.
@@ -0,0 +1,71 @@
1
+ # Side-Effects Review — Live credential re-pointing (Increment A, Step 2: CredentialLocationLedger)
2
+
3
+ **Version / slug:** `live-credential-repointing-increment-a-ledger`
4
+ **Date:** `2026-06-13`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required` (no wired consumers, no keychain writes, no live decision surface — see §4)
7
+
8
+ ## Summary of the change
9
+
10
+ Adds `src/core/CredentialLocationLedger.ts` (spec §2.2) — the durable, machine-local bookkeeping core that records, per config-home SLOT, which pool account's credential currently lives there. Plus `tests/unit/credential-location-ledger.test.ts` (17 tests). This is Step 2 of Increment A; it is **not wired into any consumer yet** (the §2.2 census re-routing is Step 6) and it performs **no keychain writes** (the staged swap executor is Step 5, the write funnel is Step 4). The identity oracle it seeds from is the injected `IdentityOracle` interface — Step 3 implements it against `api.anthropic.com/api/oauth/profile`.
11
+
12
+ Decision points the module embodies (all internal bookkeeping postures, not message/dispatch/session decisions):
13
+ - **Unknown-mode** on corrupt on-disk state: fail-closed for moves (every mutation throws), fail-open-LOUD for reads (return null + one HIGH attention item). Recovery = a fresh oracle re-seed.
14
+ - **Seed-never-guess**: a probed email mapping to ≥2 accounts (ambiguous) or 0 accounts (unknown) REFUSES auto-assignment + quarantines the slot + raises attention.
15
+ - **One-home-per-credential** invariant: re-pointing a slot evicts both the slot's prior tenant and any stale assignment of the same account elsewhere.
16
+
17
+ ## Decision-point inventory
18
+
19
+ - `CredentialLocationLedger.assertMutable` — **add** — refuses every mutation while in unknown mode (fail-closed). No external authority; throws a typed error to the (future) caller.
20
+ - `CredentialLocationLedger.seedFromOracle` — **add** — refuse-to-guess on ambiguous/unknown email; quarantine on oracle-unavailable. Produces signals (attention items, quarantine flags), never blocks anything outside the ledger.
21
+ - `slotOf` / `tenantOf` — **add** — pure in-memory reads; return null in unknown/never-seeded mode so callers fall back to today's enrollment-home behavior.
22
+
23
+ ---
24
+
25
+ ## 1. Over-block
26
+
27
+ **No block/allow surface — over-block not applicable.** The only "refusals" are (a) mutations while corrupt (fail-closed, the safe direction — the alternative is moving a credential on guessed state) and (b) refusing to auto-assign a slot whose tenant can't be uniquely resolved (the alternative is guessing the wrong account, which would route a session to someone else's credential). Both refusals are deliberately conservative; neither rejects a legitimate user input (there is no user input — it's internal bookkeeping).
28
+
29
+ ---
30
+
31
+ ## 2. Under-block
32
+
33
+ **Limited block surface.** The ledger does not attempt to detect a credential that the *client itself* rotated out from under it (the §2.3 source-slot CAS + identity audit, Step 5, owns that). A slot whose on-disk blob silently changed tenant between probes would show a stale assignment until the next scheduled audit probe (§2.4). This is by design — the ledger is the record, the audit probe is the divergence detector — and is documented in §2.11. The unknown-mode trigger only fires on *unparseable / wrong-shape* state, not on a semantically-stale-but-valid ledger; staleness is the audit's job.
34
+
35
+ ---
36
+
37
+ ## 3. Level-of-abstraction fit
38
+
39
+ Correct layer. This is a `src/core` durable-state module mirroring `SubscriptionPool` (atomic tmp+rename save, narrow injected deps). It REUSES the established patterns rather than inventing new ones: the save/load shape from `SubscriptionPool.save`, the injected-attention-callback pattern from `AgentWorktreeDetector`, and an injected oracle interface so the network-touching implementation lives one layer out (Step 3). It does not re-implement keychain access (deferred to the write funnel) or HTTP (deferred to the oracle).
40
+
41
+ ---
42
+
43
+ ## 4. Signal vs authority compliance
44
+
45
+ Compliant. The ledger is a **record + signal producer**, not an authority over agent behavior. Its strongest action is to *refuse its own mutation* (fail-closed) and to *raise an attention signal* — it never blocks an outbound message, a session spawn, or a dispatch. The identity oracle is registered conceptually as a HIGH-criticality state detector (§2.2 RULE 3.1) whose fallback is fail-closed (no answer → quarantine, never guess) — exactly the signal-vs-authority posture `docs/signal-vs-authority.md` prescribes for a brittle external probe. **Second-pass review: not required** under Phase 5 — there is no block/allow on messaging/dispatch, no session-lifecycle mutation, no wired gate/sentinel/watchdog, and no keychain write in this module. The genuinely high-risk decision logic (the staged swap executor and the live consumer re-routing) lands in Steps 4–6 and WILL carry a second-pass review there.
46
+
47
+ ---
48
+
49
+ ## 5. Interactions
50
+
51
+ - **No wired consumers yet** — the §2.2 census (QuotaPoller, SessionManager spawn, InUseAccountResolver, etc.) is re-routed in Step 6. Until then nothing reads this ledger, so it cannot shadow or race any existing check. The `state/credential-locations.json` file is new and owned solely by this module.
52
+ - **Single-writer** — `version` is a journal sequence under the server-process single-writer assumption (the per-slot write funnel + single-mover mutex are Step 4). This module does not itself spawn writers.
53
+ - **Journal pruning** keeps all in-flight + last 50 terminal entries; an in-flight entry is never pruned (so crash-recovery, Step 5, can always find an interrupted swap).
54
+
55
+ ---
56
+
57
+ ## 6. External surfaces
58
+
59
+ None visible to other agents/users/systems in this commit. No routes (`/credentials/*` is Step 7), no notices except the two internal attention items (unknown-mode, seed-refusal) which only fire on a genuine degradation and are deduped by stable id. The feature remains dark (Step 1's gate); nothing constructs this ledger at runtime yet.
60
+
61
+ ---
62
+
63
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
64
+
65
+ **Machine-local BY DESIGN** — credentials live in each machine's own keychain/config homes, so "which account is in which home" is inherently per-machine. The ledger file is `state/credential-locations.json`, machine-local, NOT replicated — replicating it would be actively wrong (machine B's keychain layout differs). A swap is always machine-local; cross-machine coordination of a topic move is handled by the existing handoff guard (composed-with in Step 5, not here). The attention items it raises are per-machine signals. There is no cross-machine read surface to proxy because the answer is only meaningful for the machine asking.
66
+
67
+ ---
68
+
69
+ ## 8. Rollback cost
70
+
71
+ Near-zero. New file + new test + a doc progress-log line; no wired consumers, no migration, no keychain mutation. Plain `git revert`. The `state/credential-locations.json` file is only ever created once the (dark) feature constructs the ledger — which nothing does yet — so even a deployed revert leaves no orphaned state.
@@ -0,0 +1,68 @@
1
+ # Side-Effects Review — Live credential re-pointing (Increment A, Step 3: CredentialIdentityOracle)
2
+
3
+ **Version / slug:** `live-credential-repointing-increment-a-oracle`
4
+ **Date:** `2026-06-13`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required` (pure read+classify signal producer, fail-closed, no authority, no writes — see §4)
7
+
8
+ ## Summary of the change
9
+
10
+ Adds `src/core/CredentialIdentityOracle.ts` (spec §2.3 verify / §2.11) — the implementation of the `IdentityOracle` interface the ledger (Step 2) already depends on. Given a config-home slot, it reads the slot's current credential blob (reusing `readClaudeOauth` from OAuthRefresher — no hand-rolled keychain access), takes the OAuth access token, and asks the read-only `GET /api/oauth/profile` endpoint which account that token belongs to. Returns the raw probed email, or an `unavailable` result on any failure. Pool-mapping (email→accountId) stays in the ledger.
11
+
12
+ Also: adds `src/core/CredentialIdentityOracle.ts` to the `lint-no-direct-llm-http.js` ALLOWLIST (the profile call is read-only identity bookkeeping, not an LLM inference call — same class as QuotaPoller's `/api/oauth/usage`), and `tests/unit/credential-identity-oracle.test.ts` (9 tests).
13
+
14
+ Not wired into any runtime construction yet — the ledger is constructed with this oracle at the route/server layer in Step 7. No keychain WRITES (the refresh-before-profile optimization for an expired token is tracked to Step 4/5 when the write funnel exists; until then an expired token classifies `unavailable`, the safe direction).
15
+
16
+ ## Decision-point inventory
17
+
18
+ - `CredentialIdentityOracle.resolveSlotTenant` — **add** — classifies a slot probe as confirmed-email or unavailable. Signal producer only; returns a value, blocks nothing. Fail-closed: every uncertain outcome → `unavailable` (never a guessed/mismatched identity).
19
+
20
+ ---
21
+
22
+ ## 1. Over-block
23
+
24
+ **No block/allow surface — over-block not applicable.** The oracle rejects nothing; it returns either an email or `unavailable`. The conservative direction (treat any non-2xx / parse-failure / missing-email as `unavailable`) means a momentarily-flaky probe yields "can't tell" → the ledger quarantines and re-probes, never a wrong assignment. That is the intended safety bias.
25
+
26
+ ---
27
+
28
+ ## 2. Under-block
29
+
30
+ **Limited.** The oracle does not currently refresh an expired access token before probing (the spec optimization needs the Step-4 write funnel). The effect is a possible spurious `unavailable` on a slot whose token just expired — which is safe (quarantine + re-probe), never a wrong identity. Tracked to Step 4/5. It also cannot detect a token that is valid but belongs to a DIFFERENT account than expected — that's exactly the point: it reports the REAL owner; the ledger/audit compares against expectation (§2.11 divergence).
31
+
32
+ ---
33
+
34
+ ## 3. Level-of-abstraction fit
35
+
36
+ Correct layer. A `src/core` detector that REUSES the established per-slot blob read (`readClaudeOauth`, OAuthRefresher) and mirrors the existing profile-call shape (QuotaCollector.oauthGet). It does not re-implement keychain access or invent a second profile-fetch convention. The fetch lives behind a bounded timeout and an injectable `fetchImpl` for tests, matching `refreshClaudeToken`'s dependency-injection style.
37
+
38
+ ---
39
+
40
+ ## 4. Signal vs authority compliance
41
+
42
+ Compliant — textbook signal producer. It reads credential reality and emits a classification; it holds no authority and performs no mutation. Per the spec's RULE 3.1 registration, it is a HIGH-criticality state detector whose fallback is fail-closed (no answer → `unavailable`, never guessed). `docs/signal-vs-authority.md` prescribes exactly this for a brittle external probe. **Second-pass review: not required** under Phase 5 — no block/allow on messaging/dispatch, no session-lifecycle, no gate/sentinel/watchdog, no write. Every classification branch is unit-tested. The high-risk WRITE logic (the staged swap executor) is Step 5 and will carry a second-pass review.
43
+
44
+ ---
45
+
46
+ ## 5. Interactions
47
+
48
+ - **Lint allowlist** — adding the file to `lint-no-direct-llm-http.js` ALLOWLIST is the only cross-file effect; it is narrowly justified (read-only OAuth identity endpoint, same class as the already-listed QuotaPoller `/usage`). It does not weaken the lint for any other file.
49
+ - **No runtime construction yet** — nothing builds this oracle at runtime in this commit, so it cannot race or shadow any existing credential reader. The ledger (Step 2) holds it as an injected interface; the server wires the concrete instance in Step 7.
50
+ - It is READ-only against the keychain (via `readClaudeOauth`); it never competes with the QuotaPoller refresh-write or the (future) swap executor.
51
+
52
+ ---
53
+
54
+ ## 6. External surfaces
55
+
56
+ One external call: `GET https://api.anthropic.com/api/oauth/profile` with the slot's own access token — the same read-only endpoint the official client and QuotaCollector already call, bounded by a 10s timeout. No new outbound surface to other agents/users. No routes added (Step 7). The token is sent only as a Bearer header to Anthropic's own endpoint and is never logged.
57
+
58
+ ---
59
+
60
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
61
+
62
+ **Machine-local BY DESIGN** — the oracle probes credentials in THIS machine's keychain/config homes (a slot only exists on the machine whose keychain holds it). Identity is resolved against the live local credential, so the probe is meaningful only on the machine asking. No replication, no proxied read; another machine's oracle answers about its own slots. This matches the ledger's machine-local posture (Step 2).
63
+
64
+ ---
65
+
66
+ ## 8. Rollback cost
67
+
68
+ Near-zero. New file + new test + one allowlist line; no runtime construction, no writes, no migration. Plain `git revert`. Because nothing constructs the oracle at runtime yet, a revert leaves no orphaned behavior.