instar 1.3.618 → 1.3.620

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 (40) hide show
  1. package/dashboard/index.html +16 -1
  2. package/dashboard/subscriptions.js +151 -6
  3. package/dist/coordination/AccountFollowMeOperatorOutbox.d.ts +42 -0
  4. package/dist/coordination/AccountFollowMeOperatorOutbox.d.ts.map +1 -0
  5. package/dist/coordination/AccountFollowMeOperatorOutbox.js +81 -0
  6. package/dist/coordination/AccountFollowMeOperatorOutbox.js.map +1 -0
  7. package/dist/coordination/AccountFollowMeSingleFlight.d.ts +68 -0
  8. package/dist/coordination/AccountFollowMeSingleFlight.d.ts.map +1 -0
  9. package/dist/coordination/AccountFollowMeSingleFlight.js +135 -0
  10. package/dist/coordination/AccountFollowMeSingleFlight.js.map +1 -0
  11. package/dist/coordination/driveFollowMeEnrollment.d.ts +80 -0
  12. package/dist/coordination/driveFollowMeEnrollment.d.ts.map +1 -0
  13. package/dist/coordination/driveFollowMeEnrollment.js +64 -0
  14. package/dist/coordination/driveFollowMeEnrollment.js.map +1 -0
  15. package/dist/coordination/enrollPointOfUseCheck.d.ts +48 -0
  16. package/dist/coordination/enrollPointOfUseCheck.d.ts.map +1 -0
  17. package/dist/coordination/enrollPointOfUseCheck.js +45 -0
  18. package/dist/coordination/enrollPointOfUseCheck.js.map +1 -0
  19. package/dist/coordination/followMeConsumerSweep.d.ts +32 -0
  20. package/dist/coordination/followMeConsumerSweep.d.ts.map +1 -0
  21. package/dist/coordination/followMeConsumerSweep.js +58 -0
  22. package/dist/coordination/followMeConsumerSweep.js.map +1 -0
  23. package/dist/core/rawTextRequestDetector.d.ts +27 -0
  24. package/dist/core/rawTextRequestDetector.d.ts.map +1 -0
  25. package/dist/core/rawTextRequestDetector.js +42 -0
  26. package/dist/core/rawTextRequestDetector.js.map +1 -0
  27. package/dist/monitoring/guardManifest.d.ts.map +1 -1
  28. package/dist/monitoring/guardManifest.js +1 -0
  29. package/dist/monitoring/guardManifest.js.map +1 -1
  30. package/dist/server/routes.d.ts.map +1 -1
  31. package/dist/server/routes.js +70 -4
  32. package/dist/server/routes.js.map +1 -1
  33. package/package.json +1 -1
  34. package/scripts/instar-dev-precommit.js +38 -1
  35. package/scripts/lib/operator-surface.mjs +63 -0
  36. package/src/data/builtin-manifest.json +46 -46
  37. package/upgrades/1.3.619.md +33 -0
  38. package/upgrades/1.3.620.md +22 -0
  39. package/upgrades/side-effects/ws52-operator-card-wiring.md +60 -0
  40. package/upgrades/side-effects/ws52-operator-tap-not-text.md +112 -0
@@ -999,6 +999,14 @@ export function createRoutes(ctx) {
999
999
  void observePrincipalCoherence(text, options.topicId).catch(() => {
1000
1000
  /* @silent-fallback-ok — observe-only; a detector error never affects delivery */
1001
1001
  });
1002
+ // ── Operators-Act-in-Taps-Not-Text Signal (OBSERVE-ONLY, ws52 Part C arm 2) ──
1003
+ // Record — but NEVER act on — an outbound message that asks the OPERATOR to paste
1004
+ // raw/technical text (JSON/fingerprints/curl/multi-step). Backstop to the arm-1
1005
+ // build-time gate; per signal-vs-authority this is a SIGNAL, not a blocker. Same
1006
+ // fire-and-forget VOID contract — it cannot block, delay, or rewrite the message.
1007
+ void observeRawTextRequest(text, options.topicId).catch(() => {
1008
+ /* @silent-fallback-ok — observe-only; a detector error never affects delivery */
1009
+ });
1002
1010
  const decision = await evaluateOutbound(text, channel, options);
1003
1011
  if (!decision.ok) {
1004
1012
  res.status(decision.status).json(decision.body);
@@ -1248,6 +1256,38 @@ export function createRoutes(ctx) {
1248
1256
  // outbound message is unaffected.
1249
1257
  }
1250
1258
  }
1259
+ // ── Operators-Act-in-Taps-Not-Text Signal (OBSERVE-ONLY, ws52 Part C arm 2) ──
1260
+ // Backstop to the arm-1 build-time gate: record — never act on — an outbound
1261
+ // message that asks the OPERATOR to paste raw/technical text. Per signal-vs-authority
1262
+ // this is a SIGNAL (records the occurrence), NOT a blocker (asking-for-JSON is not
1263
+ // irreversible, so the safety-guard block exception does not apply, and a keyword
1264
+ // blocker on a user-visible channel would wedge real conversations). Dark on the
1265
+ // fleet, live on the development agent. Records the SIGNAL (reasons + topic), never
1266
+ // the raw message text. Fire-and-forget; never affects delivery.
1267
+ async function observeRawTextRequest(text, topicId) {
1268
+ try {
1269
+ if (typeof text !== 'string' || text.trim().length === 0)
1270
+ return;
1271
+ if (!resolveDevAgentGate(undefined, ctx.config))
1272
+ return;
1273
+ const { detectRawTextRequestToOperator } = await import('../core/rawTextRequestDetector.js');
1274
+ const sig = detectRawTextRequestToOperator(text);
1275
+ if (!sig.detected)
1276
+ return;
1277
+ const file = path.join(ctx.config.stateDir, 'raw-text-request-signals.jsonl');
1278
+ try {
1279
+ fs.mkdirSync(path.dirname(file), { recursive: true });
1280
+ fs.appendFileSync(file, JSON.stringify({ ts: new Date().toISOString(), topicId: topicId ?? null, reasons: sig.reasons }) + '\n', 'utf8');
1281
+ }
1282
+ catch {
1283
+ /* @silent-fallback-ok — recording must never throw */
1284
+ }
1285
+ console.log(`[operators-act-in-taps] outbound asked operator for raw text: ${sig.reasons.join('; ')}`);
1286
+ }
1287
+ catch {
1288
+ // @silent-fallback-ok — observe-only; any error silently no-ops and delivery is unaffected.
1289
+ }
1290
+ }
1251
1291
  /**
1252
1292
  * Log a tone-gate decision. Structured output for the over-block audit tail.
1253
1293
  * Writes to stderr so it's captured in the server log.
@@ -6536,10 +6576,21 @@ export function createRoutes(ctx) {
6536
6576
  res.status(400).json({ error: 'agents must be a pair of two agent fingerprints' });
6537
6577
  return;
6538
6578
  }
6539
- if (typeof b.expiresAt !== 'string' || isNaN(Date.parse(b.expiresAt)) || Date.parse(b.expiresAt) <= Date.now()) {
6540
- res.status(400).json({ error: 'expiresAt must be a future ISO timestamp (mandates always expire)' });
6579
+ // FD1 (ws52-operator-tap-not-text): the one-tap card sends NO expiry the server
6580
+ // defaults to 1 hour (a mandate only needs to authorize one re-mint enrollment, not
6581
+ // standing access). A caller MAY still pass an explicit future expiry; an invalid one
6582
+ // is rejected. Mandates always expire either way.
6583
+ let expiresAt;
6584
+ if (b.expiresAt === undefined || b.expiresAt === null || b.expiresAt === '') {
6585
+ expiresAt = new Date(Date.now() + 60 * 60 * 1000).toISOString();
6586
+ }
6587
+ else if (typeof b.expiresAt !== 'string' || isNaN(Date.parse(b.expiresAt)) || Date.parse(b.expiresAt) <= Date.now()) {
6588
+ res.status(400).json({ error: 'expiresAt, if provided, must be a future ISO timestamp' });
6541
6589
  return;
6542
6590
  }
6591
+ else {
6592
+ expiresAt = b.expiresAt;
6593
+ }
6543
6594
  const { accountId, targetMachineId } = b;
6544
6595
  const selfMachineId = ctx.meshSelfId ?? ctx.config.machineId ?? 'local';
6545
6596
  // Issue locally (PIN-gated above) — exact-bounds account-follow-me / re-mint (R1).
@@ -6550,7 +6601,7 @@ export function createRoutes(ctx) {
6550
6601
  { action: 'account-follow-me', bounds: { accountId, targetMachineId, mechanism: 're-mint' } },
6551
6602
  ],
6552
6603
  author: typeof b.author === 'string' && b.author ? b.author : 'justin',
6553
- expiresAt: b.expiresAt,
6604
+ expiresAt,
6554
6605
  });
6555
6606
  // A LOCAL target is the ordinary path — the local gate already sees this mandate; no delivery.
6556
6607
  if (targetMachineId === selfMachineId) {
@@ -19282,7 +19333,22 @@ export function createRoutes(ctx) {
19282
19333
  },
19283
19334
  });
19284
19335
  const { offered } = svc.scanAndOffer();
19285
- res.json({ enabled: true, offered });
19336
+ // Enrich each consent offer for the one-tap Approve card (ws52-operator-tap-not-text Part A +
19337
+ // FD2). The card reads machineNickname / accountLabel / expiryText and the FD2 `agents` pair —
19338
+ // ALL resolved SERVER-SIDE so the operator never types a fingerprint or a JSON authority. The
19339
+ // agents pair is this Echo identity's routing fingerprint (issuer === target identity: the same
19340
+ // agent following its own account onto another machine); the cross-machine enroll authorization
19341
+ // (R4a delivered-mandate path) gates on the mandate's exact bounds + issuance signature, not on
19342
+ // this pair, and issue-for-machine only requires a valid two-fingerprint pair.
19343
+ const localFp = resolveAgentFingerprint(ctx);
19344
+ const enriched = offered.map((o) => ({
19345
+ ...o,
19346
+ machineNickname: o.targetMachineNickname,
19347
+ accountLabel: o.accountEmail || o.accountId,
19348
+ expiryText: 'Authorizes this one setup, then expires (1 hour).',
19349
+ agents: [localFp, localFp],
19350
+ }));
19351
+ res.json({ enabled: true, offered: enriched });
19286
19352
  }
19287
19353
  catch (err) {
19288
19354
  res.status(500).json({ error: err instanceof Error ? err.message : 'follow-me scan failed' });