instar 1.3.56 → 1.3.57
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.
- package/dist/commands/test-as-self.d.ts.map +1 -1
- package/dist/commands/test-as-self.js +2 -1
- package/dist/commands/test-as-self.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +33 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +117 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +4 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/scheduler/MentorOnboardingRunner.d.ts +10 -1
- package/dist/scheduler/MentorOnboardingRunner.d.ts.map +1 -1
- package/dist/scheduler/MentorOnboardingRunner.js.map +1 -1
- package/dist/server/AgentServer.d.ts +8 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +34 -7
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +1 -0
- package/dist/server/routes.js.map +1 -1
- package/dist/threadline/AgentDiscovery.d.ts +6 -1
- package/dist/threadline/AgentDiscovery.d.ts.map +1 -1
- package/dist/threadline/AgentDiscovery.js +3 -0
- package/dist/threadline/AgentDiscovery.js.map +1 -1
- package/dist/threadline/ThreadlineBootstrap.d.ts.map +1 -1
- package/dist/threadline/ThreadlineBootstrap.js +20 -2
- package/dist/threadline/ThreadlineBootstrap.js.map +1 -1
- package/dist/threadline/ThreadlineEndpoints.d.ts +2 -0
- package/dist/threadline/ThreadlineEndpoints.d.ts.map +1 -1
- package/dist/threadline/ThreadlineEndpoints.js +13 -1
- package/dist/threadline/ThreadlineEndpoints.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/src/scaffold/templates.ts +4 -0
- package/upgrades/1.3.56.md +16 -17
- package/upgrades/1.3.57.md +49 -0
- package/upgrades/side-effects/mentee-agent-name-and-botid-coercion.md +55 -0
- package/upgrades/side-effects/test-as-self-skill-demote.md +38 -0
- package/upgrades/side-effects/threadline-identity-discovery-unification.md +100 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Instar Upgrade Guide — NEXT
|
|
2
|
+
|
|
3
|
+
<!-- bump: patch -->
|
|
4
|
+
|
|
5
|
+
## What Changed
|
|
6
|
+
|
|
7
|
+
**1. Mentor cycle round-trip — final fixes (verified live end-to-end).** Three bugs that only surfaced when the full Echo↔Codey round-trip ran on real servers:
|
|
8
|
+
|
|
9
|
+
1. **`mentor.menteeAgentName` config.** The mentor derived the mentee's agent name as `instar-${menteeFramework}` (e.g. `instar-codex-cli`), but the mentee registers under its real name (`instar-codey`). The mismatch broke same-machine peer lookup AND the reply allowlist. New optional `mentor.menteeAgentName` (defaults to `instar-${menteeFramework}`) carries the real registry name.
|
|
10
|
+
2. **botId string coercion.** `mentor.menteeBotId` is often stored as a JSON number, but the a2a marker's `senderBotId` is always a string. The allowlist compares with `===`, so a number/string mismatch silently dropped every reply as `agent-marker-unknown`. All botId allowlist entries are now `String()`-coerced.
|
|
11
|
+
3. **`senderBotId` = sender's own bot id.** The unified transport sent the *recipient's* bot id as the inbox `senderBotId`; the recipient's allowlist check (`knownAgents[from].botId === senderBotId`) therefore always failed. It now sends the *sender's* own bot id (`ownPrimaryBotId()` for replies, the mentor bot id for sends).
|
|
12
|
+
|
|
13
|
+
With these, the mentor cycle round-trips live: a mentor prompt reaches the mentee, the mentee spawns a session + replies, and the reply lands in the mentor's `mentor-replies.jsonl`.
|
|
14
|
+
|
|
15
|
+
**2. Threadline identity-discovery unification** (spec `docs/specs/threadline-identity-discovery-unification.md`, approved). Fixes a fleet-wide bug where an agent advertised a Threadline identity its own relay does not answer to — so peers who discovered it got a dead address and their messages silently vanished.
|
|
16
|
+
|
|
17
|
+
Discovery (`agent-info.json`) and `/threadline/health` now advertise the **routing fingerprint** — the address the relay actually registers with — plus a `publicKey`/`identityPub` set to the SAME canonical `identity.json` key, so the two are internally consistent (`fingerprint === computeFingerprint(publicKey)`). Both are resolved via `IdentityManager.get()`, the exact read-only call the relay client uses, so discovery and routing can never re-diverge.
|
|
18
|
+
|
|
19
|
+
## What to Tell Your User
|
|
20
|
+
|
|
21
|
+
- The cross-agent mentor cycle now works fully end-to-end on the same machine — verified with a real round-trip. These were the last three wiring bugs (an agent-name assumption, a number-vs-string comparison, and a sender-identity mixup). No config changes required unless your mentee's registry name differs from the default (instar plus the framework name), in which case set the mentee agent name in config.
|
|
22
|
+
- If another agent's messages to me were vanishing (their side said "sent" but nothing arrived), the Threadline fix addresses it: I was handing out an address my relay doesn't listen on. Now I publish the right one everywhere, and existing agents are repaired automatically on the next update/restart — no action needed.
|
|
23
|
+
|
|
24
|
+
## Summary of New Capabilities
|
|
25
|
+
|
|
26
|
+
| Capability | How to Use |
|
|
27
|
+
|-----------|-----------|
|
|
28
|
+
| `mentor.menteeAgentName` | Set in `.instar/config.json` when the mentee's agent-registry name differs from `instar-<menteeFramework>` (e.g. `instar-codey`). Defaults to the framework-derived name. |
|
|
29
|
+
| Robust botId matching | botId allowlist entries are string-coerced; `senderBotId` is the sender's own bot id. No action needed — fixes silent reply drops. |
|
|
30
|
+
| `fingerprint` field in `/threadline/health` + `agent-info.json` | `curl localhost:PORT/threadline/health` → `fingerprint` is the routable relay address |
|
|
31
|
+
| Threadline fleet repair migrator | `migrateThreadlineAgentInfoIdentity` runs on update; rewrites a diverged `agent-info.json` to the canonical consistent pair (no-op when already aligned or no routing identity) |
|
|
32
|
+
|
|
33
|
+
## Behavior Notes (Threadline)
|
|
34
|
+
|
|
35
|
+
- **No fabrication:** an agent with no resolvable routing identity (none on disk, or a locked-encrypted `identity.json`) now OMITS `fingerprint`/`publicKey` from discovery instead of inventing a dead address. It becomes relay-discoverable once it has a routing identity (self-heals on the next boot).
|
|
36
|
+
- **Safe encoding/consumer story:** `publicKey` stays hex and is a valid 32-byte Ed25519 key; `/threadline/health.identityPub` switches to the canonical key only when one resolves. The E2E handshake exchanges keys inline (does not read these fields) and `verifyAgent` does no real crypto challenge, so the switch is safe.
|
|
37
|
+
|
|
38
|
+
## Evidence
|
|
39
|
+
|
|
40
|
+
- **Mentor cycle:** extended `mentor-reply-via-inbox` E2E with a second case proving the menteeAgentName override + numeric-configured botId (string senderBotId) routes + persists. Both cases green; all prior mentor/mentee/inbox tests green. **Live-verified**: full Echo→Codey→Echo round-trip persisted to `mentor-replies.jsonl`. Side-effects: `upgrades/side-effects/mentee-agent-name-and-botid-coercion.md`.
|
|
41
|
+
- **Threadline — wiring (load-bearing):** boot the Threadline stack with a seeded canonical identity → `agent-info.json.fingerprint` equals `IdentityManager.getOrCreate().fingerprint` (the relay's registration source). `tests/e2e/threadline/identity-discovery-wiring.test.ts`.
|
|
42
|
+
- **Threadline — integration:** `/threadline/health` returns a non-empty `identityPub` + a `fingerprint` equal to the relay-registration fingerprint, internally consistent. `tests/integration/threadline/identity-discovery-health.test.ts`.
|
|
43
|
+
- **Threadline — unit (both-sides):** identity present → consistent pair advertised; no/locked identity → both omitted; orphan `identity-keys.json` present → canonical advertised, never the orphan hex. `ThreadlineBootstrap.test.ts`, `ThreadlineEndpoints.test.ts`.
|
|
44
|
+
- **Threadline — migrator:** diverged → repaired; aligned → no-op; no-identity → no-op (no fabrication); idempotent. `tests/unit/PostUpdateMigrator-threadlineAgentInfoIdentity.test.ts`. Verified end-to-end via test-as-self against the built dist (positive + no-identity boundary).
|
|
45
|
+
|
|
46
|
+
## Out of Scope (Threadline — tracked separately)
|
|
47
|
+
|
|
48
|
+
- Multi-machine same-fingerprint advertisement coordination (cross-machine seamlessness spec). This fix sets the `machine` field and is neutral for multi-machine.
|
|
49
|
+
- Retiring the orphan `identity-keys.json` / collapsing the keypairs. This fix stops *advertising* the orphan; deleting it touches the handshake layer and needs its own spec.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Side-Effects Review — menteeAgentName + botId coercion + senderBotId fix
|
|
2
|
+
|
|
3
|
+
**Spec:** `docs/specs/MENTOR-LIVE-READINESS-SPEC.md` §Recipient side. Final
|
|
4
|
+
fixes closing the live round-trip (the reply leg landed in #469; these are the
|
|
5
|
+
three remaining wiring bugs found by running it end-to-end).
|
|
6
|
+
|
|
7
|
+
**Change:** `src/scheduler/MentorOnboardingRunner.ts` (+1 optional config
|
|
8
|
+
field) + `src/server/AgentServer.ts` (menteeAgentName usage at 3 derivation
|
|
9
|
+
sites, botId String-coercion at 3 allowlist sites, senderBotId=sender's-own
|
|
10
|
+
via new `ownPrimaryBotId()` helper) + extended E2E test.
|
|
11
|
+
|
|
12
|
+
## What changed
|
|
13
|
+
1. `MentorConfig.menteeAgentName?: string` — the mentee's real registry name.
|
|
14
|
+
Used in `deliverToMentee`, `installMentorMessageHook` (mentor side), and
|
|
15
|
+
`installMentorReceiverHook`, each falling back to `instar-${menteeFramework}`.
|
|
16
|
+
2. botId `String()`-coercion at every knownAgents construction (mentor side,
|
|
17
|
+
mentee side, receiver hook) — config may store a JSON number; the marker
|
|
18
|
+
senderBotId is always a string; the allowlist uses `===`.
|
|
19
|
+
3. `deliverA2aMessage` now takes `fromBotId` (sender's own bot id) and sends
|
|
20
|
+
it as the inbox `senderBotId`. `ownPrimaryBotId()` derives this agent's bot
|
|
21
|
+
id from its messaging config token. Replies pass `ownPrimaryBotId()`; sends
|
|
22
|
+
pass the mentor bot id.
|
|
23
|
+
|
|
24
|
+
## The seven questions
|
|
25
|
+
1. **Over-block.** N/A — these LOOSEN an over-strict (buggy) comparison that
|
|
26
|
+
was dropping legitimate replies. Coercion only makes a number match its
|
|
27
|
+
string form; it does not widen the allowlist to new agents.
|
|
28
|
+
2. **Under-block.** The allowlist still requires an exact (coerced) botId
|
|
29
|
+
match + known agent name. Spoof defense intact — a wrong botId still drops.
|
|
30
|
+
3. **Level-of-abstraction fit.** Small, local fixes at the exact sites; one
|
|
31
|
+
tiny helper (`ownPrimaryBotId`). No new infrastructure.
|
|
32
|
+
4. **Signal vs authority.** Unchanged — allowlist still decides route/drop.
|
|
33
|
+
5. **Interactions.** `menteeAgentName` defaults preserve back-compat for any
|
|
34
|
+
agent whose registry name already equals `instar-<framework>`. Coercion is
|
|
35
|
+
idempotent on already-string values.
|
|
36
|
+
6. **External surfaces.** One new optional config key (`mentor.menteeAgentName`).
|
|
37
|
+
No new routes.
|
|
38
|
+
7. **Rollback cost.** Trivial — revert restores framework-derived names + the
|
|
39
|
+
number/string comparison (re-introducing the silent reply drop).
|
|
40
|
+
|
|
41
|
+
## Testing
|
|
42
|
+
Extended `mentor-reply-via-inbox` E2E (now 2 cases): the original
|
|
43
|
+
(framework-derived name, string botId) + the new regression (menteeAgentName
|
|
44
|
+
override, numeric-configured botId, string senderBotId) — both route + persist.
|
|
45
|
+
All prior mentor/mentee/inbox tests green. tsc clean.
|
|
46
|
+
|
|
47
|
+
**Live verification (test-as-self):** the worktree build was shadow-deployed to
|
|
48
|
+
the live Echo + Codey pair; a real mentor prompt round-tripped — Codey spawned
|
|
49
|
+
a mentee session, replied via /a2a/inbox, and Echo persisted the reply to
|
|
50
|
+
mentor-replies.jsonl (corr=FULL-…, from=instar-codey, transport=a2a-inbox-local).
|
|
51
|
+
|
|
52
|
+
## Migration parity
|
|
53
|
+
`mentor.menteeAgentName` is optional with a back-compat default — no migration
|
|
54
|
+
needed. The botId coercion + senderBotId fix are pure code (no config/state
|
|
55
|
+
change). No PostUpdateMigrator change.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Side-Effects Review — test-as-self SKILL Part 2.1 demote + migrator (Track F follow-up)
|
|
2
|
+
|
|
3
|
+
**Scope.** `.claude/skills/test-as-self/SKILL.md` (lead with the one-button
|
|
4
|
+
command, demote the manual recipe to fallback), `src/core/PostUpdateMigrator.ts`
|
|
5
|
+
(`migrateTestAsSelfSkill` + registration), `tests/unit/migrate-test-as-self-skill.test.ts`.
|
|
6
|
+
|
|
7
|
+
**What + why.** Track F shipped the `instar test-as-self` command but left the
|
|
8
|
+
SKILL.md as the v1 manual runbook (deferred, tracked). This closes that loop:
|
|
9
|
+
the SKILL now leads with "## The one-button path (Part 2.1 — use this first)"
|
|
10
|
+
(the command), with the manual recipe demoted to fallback — so an agent reading
|
|
11
|
+
the skill reaches for the command first. Plus a PostUpdateMigrator method so
|
|
12
|
+
EXISTING agents' on-disk SKILL.md gets the update (installBuiltinSkills is
|
|
13
|
+
install-if-missing, so a dedicated migration is the only path per the Migration
|
|
14
|
+
Parity Standard).
|
|
15
|
+
|
|
16
|
+
**Side-effects review.**
|
|
17
|
+
- **Content-only skill change** — no behavior change; the command itself shipped
|
|
18
|
+
in Track F (#472). This just makes it discoverable in the skill surface.
|
|
19
|
+
- **Migration is conservative + idempotent** — re-copies the bundled SKILL only
|
|
20
|
+
when the installed copy LACKS the Part 2.1 marker AND still matches the stock
|
|
21
|
+
fingerprint (`Throwaway-Deploy Harness` + `verify.mjs`). A customized skill is
|
|
22
|
+
left untouched (recorded in result.skipped); an already-updated one is a no-op;
|
|
23
|
+
a not-installed one is a no-op. Mirrors the proven migrateBuildSkillMethodology
|
|
24
|
+
pattern exactly.
|
|
25
|
+
- **No data/secret impact** — pure doc/skill content.
|
|
26
|
+
|
|
27
|
+
**Test coverage.** Unit `tests/unit/migrate-test-as-self-skill.test.ts` (4):
|
|
28
|
+
stock-v1 → updated (marker present, result.upgraded set); idempotent (already-
|
|
29
|
+
marked → unchanged, no upgrade); customized → untouched (result.skipped);
|
|
30
|
+
not-installed → no-op (no error). All green; tsc clean. The test also implicitly
|
|
31
|
+
verifies the bundled SKILL.md carries the new marker.
|
|
32
|
+
|
|
33
|
+
**Migration parity.** This IS the migration-parity fix for Track F's SKILL
|
|
34
|
+
update. New agents get the new SKILL via installBuiltinSkills; existing agents
|
|
35
|
+
get it via `migrateTestAsSelfSkill` on update.
|
|
36
|
+
|
|
37
|
+
**Rollback.** Revert. The SKILL reverts to the v1 manual-first runbook; the
|
|
38
|
+
command still works (it's independent). No data change.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Side-Effects Review — Threadline Identity-Discovery Unification
|
|
2
|
+
|
|
3
|
+
**Slug:** `threadline-identity-discovery-unification`
|
|
4
|
+
**Date:** 2026-05-28
|
|
5
|
+
**Author:** Echo (instar developer)
|
|
6
|
+
**Spec:** `docs/specs/threadline-identity-discovery-unification.md` (converged iter 2, approved)
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Discovery was advertising an identity the agent's own relay does not answer to.
|
|
11
|
+
`announcePresence` wrote `agent-info.json.publicKey` from the orphan
|
|
12
|
+
`threadline/identity-keys.json` hex key, and `/threadline/health` reported
|
|
13
|
+
`identityPub` from the HandshakeManager key (empty on some agents) — while the
|
|
14
|
+
relay client registers (and routes) by the canonical `identity.json`
|
|
15
|
+
fingerprint. A peer who discovered the agent got a non-routable address, so
|
|
16
|
+
their messages vanished (their side `sent=true`, no "Accepted message from …"
|
|
17
|
+
on the receiver). Fleet-wide for any agent whose canonical identity was
|
|
18
|
+
generated independently of its legacy keys.
|
|
19
|
+
|
|
20
|
+
The fix advertises the **routing identity** everywhere, internally consistent:
|
|
21
|
+
`agent-info.json` and `/threadline/health` now publish a `fingerprint`
|
|
22
|
+
(routable relay address) plus a `publicKey`/`identityPub` set to the SAME
|
|
23
|
+
canonical key, sourced from `IdentityManager.get()` — the exact read-only API
|
|
24
|
+
the relay client uses, so the two can never re-diverge.
|
|
25
|
+
|
|
26
|
+
**Files changed (source):**
|
|
27
|
+
- `src/threadline/AgentDiscovery.ts` — `fingerprint` field on `AgentInfoFile`,
|
|
28
|
+
`ThreadlineAgentInfo`, `HealthResponse`; `announcePresence` accepts +
|
|
29
|
+
persists it; `pingThreadlineHealth`/`verifyAgent` propagate it.
|
|
30
|
+
- `src/threadline/ThreadlineBootstrap.ts` — resolve `IdentityManager.get()` up
|
|
31
|
+
front; advertise canonical `publicKey`(hex)+`fingerprint`+`machine`; omit both
|
|
32
|
+
when `get()` is null (no fabrication).
|
|
33
|
+
- `src/threadline/ThreadlineEndpoints.ts` — `/threadline/health` sources
|
|
34
|
+
`identityPub`+`fingerprint` from `IdentityManager.get()`; `stateDir` added to
|
|
35
|
+
config; falls back to the handshake key + omits fingerprint when no routing
|
|
36
|
+
identity resolves.
|
|
37
|
+
- `src/server/routes.ts` — pass `stateDir` to `createThreadlineRoutes`.
|
|
38
|
+
- `src/core/PostUpdateMigrator.ts` — `migrateThreadlineAgentInfoIdentity`
|
|
39
|
+
(belt-and-suspenders fleet repair) + `migrateClaudeMd` routing-fingerprint
|
|
40
|
+
guidance section.
|
|
41
|
+
- `src/scaffold/templates.ts` — CLAUDE.md template "What address reaches me"
|
|
42
|
+
section (Agent Awareness).
|
|
43
|
+
- `src/data/builtin-manifest.json` — regenerated by the build (template hashes).
|
|
44
|
+
|
|
45
|
+
**Files changed (tests):** `ThreadlineBootstrap.test.ts`,
|
|
46
|
+
`ThreadlineEndpoints.test.ts` (+3 new: migrator unit, health integration,
|
|
47
|
+
bootstrap wiring e2e).
|
|
48
|
+
|
|
49
|
+
**Drive-by (unrelated, green-keeping):** `src/commands/test-as-self.ts` line 60
|
|
50
|
+
— `new URL(import.meta.url).pathname` → `fileURLToPath(import.meta.url)`. This
|
|
51
|
+
is the URL-pathname-path-encoding pattern (breaks when the project path
|
|
52
|
+
contains spaces) that the pre-push gate (`scripts/pre-push-gate.js`) flags; it
|
|
53
|
+
was introduced into `test-as-self.ts` by an earlier merge and the pre-push gate
|
|
54
|
+
blocks any push until it is fixed (the check is pre-push-only, not in CI, which
|
|
55
|
+
is why it landed). One-line fix to the documented-correct pattern; no behavior
|
|
56
|
+
change.
|
|
57
|
+
|
|
58
|
+
## Blast radius / consumers
|
|
59
|
+
|
|
60
|
+
- **`agent-info.json.publicKey` encoding** is unchanged (hex) and now sourced
|
|
61
|
+
from the canonical key. Consumers (`verifyAgent` does `Buffer.from(.., 'hex')`
|
|
62
|
+
+ 32-byte length check) still work; the key is a valid 32-byte Ed25519 key.
|
|
63
|
+
- **`/threadline/health.identityPub`** switches from the handshake key to the
|
|
64
|
+
canonical routing key WHEN one resolves. Safe: `verifyAgent` does not perform
|
|
65
|
+
a real cryptographic challenge (nonce generated, never exchanged), and the E2E
|
|
66
|
+
handshake exchanges keys inline — it does not read `agent-info.json` or the
|
|
67
|
+
health `identityPub`. Verified by reading `ThreadlineEndpoints.ts` handshake
|
|
68
|
+
paths + `AgentDiscovery.verifyAgent`.
|
|
69
|
+
- **No-fabrication boundary:** a relay-disabled / no-identity / locked-encrypted
|
|
70
|
+
agent now OMITS `fingerprint`+`publicKey` instead of advertising a key for an
|
|
71
|
+
identity on no relay. This is a behavior change for brand-new agents on their
|
|
72
|
+
very first announce (before the relay client creates an identity) — correct
|
|
73
|
+
per spec (not relay-discoverable until a routing identity exists), self-heals
|
|
74
|
+
on the next boot once the identity is created.
|
|
75
|
+
- **Migrator** only rewrites `agent-info.json` when `get()` resolves AND the
|
|
76
|
+
file is misaligned; no-ops otherwise (idempotent, atomic write, never
|
|
77
|
+
fabricates).
|
|
78
|
+
|
|
79
|
+
## Migration parity
|
|
80
|
+
|
|
81
|
+
Existing agents: the in-`announcePresence` rewrite self-heals on the
|
|
82
|
+
post-update server restart; `migrateThreadlineAgentInfoIdentity` closes the
|
|
83
|
+
narrow update-without-restart window; `migrateClaudeMd` adds the guidance for
|
|
84
|
+
agents whose CLAUDE.md predates this. New agents get all of it via `init` +
|
|
85
|
+
the template.
|
|
86
|
+
|
|
87
|
+
## Out of scope (tracked, not deferred)
|
|
88
|
+
|
|
89
|
+
- Multi-machine same-fingerprint advertisement coordination →
|
|
90
|
+
`project_cross_machine_seamlessness_spec`. This fix sets the `machine` field
|
|
91
|
+
(attributable) and is neutral for multi-machine.
|
|
92
|
+
- Retiring the orphan `identity-keys.json` / collapsing the 3–4 keypairs →
|
|
93
|
+
`bug_threadline_identity_divergence_discovery`. This fix stops *advertising*
|
|
94
|
+
the orphan; deleting it touches the handshake layer and needs its own spec.
|
|
95
|
+
|
|
96
|
+
## Risk
|
|
97
|
+
|
|
98
|
+
Low. Additive at the field level; `agent-info.json` is regenerated on every
|
|
99
|
+
boot regardless. Reverting removes the field + migrator with no data loss. All
|
|
100
|
+
three test tiers + wiring + both-sides green; full `test:push` suite green.
|