@agfpd/iapeer-memory 0.1.8 → 0.1.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agfpd/iapeer-memory",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "iapeer-memory — peer memory for the iapeer ecosystem: vault, memoryd (index/search/MCP-http), layer-5 context fragments, role doctrines. The package IS the system; the claude/codex plugins are thin session sockets (docs/10-distribution.md, ADR-009).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -27,7 +27,7 @@
27
27
  "access": "public"
28
28
  },
29
29
  "dependencies": {
30
- "@agfpd/iapeer-memory-core": "0.1.8"
30
+ "@agfpd/iapeer-memory-core": "0.1.9"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/bun": "^1.2.0",
@@ -33,7 +33,7 @@ import { installBinary } from "../binary.js";
33
33
  import { memoryPaths } from "../paths.js";
34
34
  import { provisionVault, writeDefaultConfig } from "../provision.js";
35
35
  import { writeRolesManifest, type RoleEntry } from "../roles.js";
36
- import { writeSlot } from "../slot.js";
36
+ import { applyMemoryPlugin, writeSlot } from "../slot.js";
37
37
  import {
38
38
  doctrineOwnership,
39
39
  guideText,
@@ -388,7 +388,8 @@ export async function cmdInit(argv: string[]): Promise<number> {
388
388
  );
389
389
  }
390
390
 
391
- // 8. slot declaration (the contract: written by the provider, atomic)
391
+ // 8. slot declaration (the contract: written by the provider, atomic)
392
+ // v1.1: includes the plugin block the core derives installs from.
392
393
  const slot = writeSlot({
393
394
  slotPath: paths.slotPath,
394
395
  version,
@@ -402,6 +403,26 @@ export async function cmdInit(argv: string[]): Promise<number> {
402
403
  slot.action !== "refused-foreign",
403
404
  );
404
405
 
406
+ // 8b. session plugin across the fleet — the core verb derives the plugin
407
+ // from the block JUST written (order matters), installs per-peer on claude
408
+ // and host-globally on codex; new peers get it from the core's birth-hook.
409
+ // Soft-skip on an older core (verb landed in iapeer 0.2.25).
410
+ if (flags.skipEcosystem) {
411
+ step("plugin", "skipped (--skip-ecosystem)");
412
+ } else if (slot.action === "refused-foreign") {
413
+ step("plugin", "skipped (slot refused — nothing to derive the plugin from)");
414
+ } else {
415
+ const plug = applyMemoryPlugin({ mode: "on", iapeerBin: flags.iapeerBin });
416
+ step(
417
+ "plugin",
418
+ plug.suppressed
419
+ ? "skipped (test sandbox — core calls suppressed)"
420
+ : plug.ok
421
+ ? "session plugin rolled out across the fleet (iapeer memory-plugin on --all)"
422
+ : `soft-skip: ${plug.detail.slice(0, 160) || "verb failed"} — upgrade the iapeer core (≥0.2.25) or run manually: iapeer memory-plugin on --all`,
423
+ );
424
+ }
425
+
405
426
  // 9. native-memory sweep — the core's lever (one home of runtime forms);
406
427
  // soft-skip when the verb is unavailable (older core), verify re-runs later.
407
428
  if (flags.skipEcosystem) {
@@ -18,7 +18,7 @@
18
18
  import fs from "node:fs";
19
19
  import { memoryPaths } from "../paths.js";
20
20
  import { removeBinary } from "../binary.js";
21
- import { removeSlot } from "../slot.js";
21
+ import { applyMemoryPlugin, readSlot, removeSlot, SLOT_PROVIDER } from "../slot.js";
22
22
  import {
23
23
  DREAM_TRIGGER_ID,
24
24
  SWEEP_TRIGGER_ID,
@@ -95,6 +95,28 @@ export function cmdUninstall(argv: string[]): number {
95
95
  const paths = memoryPaths();
96
96
  let failed = false;
97
97
 
98
+ // Session plugin OFF across the fleet BEFORE removing the declaration —
99
+ // the core verb DERIVES the plugin identity from the slot's v1.1 block;
100
+ // once the declaration is gone there is nothing to derive from (agreed
101
+ // order with the core, auto-removal: a dead provider's plugin must not
102
+ // keep injecting). Guard: only when the slot is OURS — running `off`
103
+ // against a foreign declaration would strip the FOREIGN provider's plugin.
104
+ // codex nuance: the codex plugin is host-global, so `off` there is always
105
+ // `--all` semantics; per-peer off on codex answers «use --all».
106
+ const declared = readSlot(paths.slotPath);
107
+ if (declared && declared.provider === SLOT_PROVIDER) {
108
+ const off = applyMemoryPlugin({ mode: "off", iapeerBin });
109
+ console.log(
110
+ `plugin : ${
111
+ off.suppressed
112
+ ? "skipped (test sandbox — core calls suppressed)"
113
+ : off.ok
114
+ ? "session plugin removed across the fleet (memory-plugin off --all; codex side is host-global)"
115
+ : `off not applied (${off.detail.slice(0, 160)}) — manual fallback (works without the slot): per claude peer \`claude plugin uninstall iapeer-memory@agfpd --scope project\` from its cwd; codex (host-global): \`codex plugin remove iapeer-memory@agfpd\``
116
+ }`,
117
+ );
118
+ }
119
+
98
120
  const slot = removeSlot(paths.slotPath);
99
121
  if (slot === "refused-foreign") {
100
122
  console.log("slot : held by a FOREIGN provider — left intact");
package/src/slot.ts CHANGED
@@ -11,7 +11,15 @@
11
11
  * - `version` = the package version (the same single source as the doctrine
12
12
  * marker, ADR-010); our `update` re-writes it (P4 obligation);
13
13
  * - `heartbeat` (optional) = the absolute path whose mtime memoryd touches —
14
- * the core may show staleness in `iapeer status`, never acts on it.
14
+ * the core may show staleness in `iapeer status`, never acts on it;
15
+ * - `plugin` (v1.1, agreed 10.06 + live in iapeer 0.2.25) = the marketplace
16
+ * identity of the session plugin. The core DERIVES installs from this block:
17
+ * birth-hook installs for new peers, `iapeer memory-plugin on|off (--peer|
18
+ * --all)` is the operator verb (built-in marketplace ensure + stale-cache
19
+ * retry). Reference reader: iapeer src/status/index.ts parsePluginBlock —
20
+ * all three fields required non-empty, anything less = treated as a v1
21
+ * declaration (no install). marketplaceRef matches iapeer onboard's
22
+ * MARKETPLACE_REF for the distribution default.
15
23
  */
16
24
 
17
25
  import fs from "node:fs";
@@ -20,12 +28,29 @@ import path from "node:path";
20
28
  export const SLOT_PROVIDER = "iapeer-memory";
21
29
  export const SLOT_PACKAGE = "@agfpd/iapeer-memory";
22
30
 
31
+ /** Mirror of iapeer's MemoryProviderPlugin (src/status/index.ts). */
32
+ export type MemoryProviderPlugin = {
33
+ /** Plugin id in the marketplace (forms `<name>@<marketplace>`). */
34
+ name: string;
35
+ /** Marketplace NAME the plugin id keys on. */
36
+ marketplace: string;
37
+ /** Source ref for `plugin marketplace add` when absent on the host (owner/repo). */
38
+ marketplaceRef: string;
39
+ };
40
+
41
+ export const SLOT_PLUGIN: MemoryProviderPlugin = {
42
+ name: "iapeer-memory",
43
+ marketplace: "agfpd",
44
+ marketplaceRef: "agfpd/agfpd-marketplace",
45
+ };
46
+
23
47
  export type MemoryProviderSlot = {
24
48
  provider: string;
25
49
  package: string;
26
50
  version: string;
27
51
  registeredAt: string;
28
52
  heartbeat?: string;
53
+ plugin?: MemoryProviderPlugin;
29
54
  };
30
55
 
31
56
  /** Never throws: missing / unreadable / malformed → null (empty slot). */
@@ -59,7 +84,11 @@ export function writeSlot(opts: {
59
84
  existing &&
60
85
  existing.version === opts.version &&
61
86
  existing.heartbeat === opts.heartbeat &&
62
- existing.package === SLOT_PACKAGE
87
+ existing.package === SLOT_PACKAGE &&
88
+ existing.plugin &&
89
+ existing.plugin.name === SLOT_PLUGIN.name &&
90
+ existing.plugin.marketplace === SLOT_PLUGIN.marketplace &&
91
+ existing.plugin.marketplaceRef === SLOT_PLUGIN.marketplaceRef
63
92
  ) {
64
93
  return { action: "identical", existing }; // idempotent re-init: no churn
65
94
  }
@@ -69,6 +98,7 @@ export function writeSlot(opts: {
69
98
  version: opts.version,
70
99
  registeredAt: opts.nowIso ?? new Date().toISOString(),
71
100
  ...(opts.heartbeat ? { heartbeat: opts.heartbeat } : {}),
101
+ plugin: SLOT_PLUGIN,
72
102
  };
73
103
  fs.mkdirSync(path.dirname(opts.slotPath), { recursive: true });
74
104
  const tmp = `${opts.slotPath}.tmp`;
@@ -87,3 +117,57 @@ export function removeSlot(slotPath: string): SlotRemoveResult {
87
117
  fs.unlinkSync(slotPath);
88
118
  return "removed";
89
119
  }
120
+
121
+ export type MemoryPluginApplyResult = {
122
+ ok: boolean;
123
+ detail: string;
124
+ /** True when the test-sandbox fuse blocked the call — callers report a
125
+ * SKIP, not a failure (same contract as watcher.ts iapSend). */
126
+ suppressed?: boolean;
127
+ };
128
+
129
+ /**
130
+ * Fleet-wide install/remove of the slot-declared session plugin via the core
131
+ * verb `iapeer memory-plugin <on|off> --all` (iapeer ≥0.2.25; marketplace
132
+ * ensure + stale-cache retry live INSIDE the verb). The verb derives the
133
+ * plugin identity from the slot declaration — so `on` runs AFTER writeSlot
134
+ * and `off` runs BEFORE removeSlot (agreed order, auto-removal: a dead
135
+ * provider's plugin must not keep injecting).
136
+ *
137
+ * Hard fuse first (same class as iapSend, incident 10.06): `on --all`
138
+ * mutates the HOST fleet — no sandbox env contains it, tests must never
139
+ * reach the live core. Both belts honoured.
140
+ */
141
+ export function applyMemoryPlugin(opts: {
142
+ mode: "on" | "off";
143
+ iapeerBin?: string;
144
+ }): MemoryPluginApplyResult {
145
+ if (
146
+ process.env.IAPEER_MEMORY_SUPPRESS_IAP_SEND === "1" ||
147
+ process.env.IAPEER_TEST_SANDBOX === "1"
148
+ ) {
149
+ return {
150
+ ok: false,
151
+ suppressed: true,
152
+ detail: "memory-plugin call suppressed (test sandbox)",
153
+ };
154
+ }
155
+ const bin = opts.iapeerBin ?? "iapeer";
156
+ try {
157
+ const proc = Bun.spawnSync([bin, "memory-plugin", opts.mode, "--all"], {
158
+ stdout: "pipe",
159
+ stderr: "pipe",
160
+ });
161
+ if (proc.exitCode !== 0) {
162
+ return {
163
+ ok: false,
164
+ detail:
165
+ (proc.stderr.toString().trim() || proc.stdout.toString().trim() || "").slice(0, 200) ||
166
+ `iapeer memory-plugin exited ${proc.exitCode}`,
167
+ };
168
+ }
169
+ return { ok: true, detail: proc.stdout.toString().trim() };
170
+ } catch (err) {
171
+ return { ok: false, detail: `${bin} unavailable: ${String(err)}` };
172
+ }
173
+ }