@jungjaehoon/mama-os 0.48.2 → 0.48.3

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 (52) hide show
  1. package/dist/agent/code-act/host-bridge.d.ts.map +1 -1
  2. package/dist/agent/code-act/host-bridge.js +28 -1
  3. package/dist/agent/code-act/host-bridge.js.map +1 -1
  4. package/dist/agent/gateway-tool-executor.d.ts +8 -0
  5. package/dist/agent/gateway-tool-executor.d.ts.map +1 -1
  6. package/dist/agent/gateway-tool-executor.js +271 -17
  7. package/dist/agent/gateway-tool-executor.js.map +1 -1
  8. package/dist/agent/gateway-tools.md +2 -1
  9. package/dist/agent/obsidian-cli-args.d.ts +14 -0
  10. package/dist/agent/obsidian-cli-args.d.ts.map +1 -0
  11. package/dist/agent/obsidian-cli-args.js +57 -0
  12. package/dist/agent/obsidian-cli-args.js.map +1 -0
  13. package/dist/agent/tool-registry.d.ts.map +1 -1
  14. package/dist/agent/tool-registry.js +22 -1
  15. package/dist/agent/tool-registry.js.map +1 -1
  16. package/dist/agent/types.d.ts +6 -1
  17. package/dist/agent/types.d.ts.map +1 -1
  18. package/dist/agent/types.js.map +1 -1
  19. package/dist/cli/commands/start.d.ts.map +1 -1
  20. package/dist/cli/commands/start.js +25 -5
  21. package/dist/cli/commands/start.js.map +1 -1
  22. package/dist/cli/runtime/api-routes-init.d.ts.map +1 -1
  23. package/dist/cli/runtime/api-routes-init.js +12 -5
  24. package/dist/cli/runtime/api-routes-init.js.map +1 -1
  25. package/dist/gateways/message-router.d.ts.map +1 -1
  26. package/dist/gateways/message-router.js +1 -0
  27. package/dist/gateways/message-router.js.map +1 -1
  28. package/dist/operator/wiki-continuity.d.ts +2 -0
  29. package/dist/operator/wiki-continuity.d.ts.map +1 -1
  30. package/dist/operator/wiki-continuity.js +6 -0
  31. package/dist/operator/wiki-continuity.js.map +1 -1
  32. package/dist/operator/workorder-hooks.d.ts +3 -3
  33. package/dist/operator/workorder-hooks.d.ts.map +1 -1
  34. package/dist/operator/workorder-hooks.js +12 -23
  35. package/dist/operator/workorder-hooks.js.map +1 -1
  36. package/dist/wiki/obsidian-writer.d.ts +11 -1
  37. package/dist/wiki/obsidian-writer.d.ts.map +1 -1
  38. package/dist/wiki/obsidian-writer.js +98 -4
  39. package/dist/wiki/obsidian-writer.js.map +1 -1
  40. package/dist/wiki/wiki-read.d.ts +51 -0
  41. package/dist/wiki/wiki-read.d.ts.map +1 -0
  42. package/dist/wiki/wiki-read.js +212 -0
  43. package/dist/wiki/wiki-read.js.map +1 -0
  44. package/dist/wiki/wiki-turn-contract.d.ts.map +1 -1
  45. package/dist/wiki/wiki-turn-contract.js +6 -5
  46. package/dist/wiki/wiki-turn-contract.js.map +1 -1
  47. package/dist/wiki-artifacts/types.d.ts +4 -0
  48. package/dist/wiki-artifacts/types.d.ts.map +1 -1
  49. package/dist/wiki-artifacts/wiki-publish-adapter.d.ts.map +1 -1
  50. package/dist/wiki-artifacts/wiki-publish-adapter.js +3 -0
  51. package/dist/wiki-artifacts/wiki-publish-adapter.js.map +1 -1
  52. package/package.json +1 -1
@@ -82,6 +82,8 @@ const config_manager_js_1 = require("../cli/config/config-manager.js");
82
82
  const agent_store_js_1 = require("../db/agent-store.js");
83
83
  const index_js_1 = require("../envelope/index.js");
84
84
  const wiki_publish_adapter_js_1 = require("../wiki-artifacts/wiki-publish-adapter.js");
85
+ const obsidian_cli_args_js_1 = require("./obsidian-cli-args.js");
86
+ const wiki_read_js_1 = require("../wiki/wiki-read.js");
85
87
  const changes_projection_js_1 = require("../operator/changes-projection.js");
86
88
  const task_list_views_js_1 = require("../operator/task-list-views.js");
87
89
  const board_read_views_js_1 = require("../operator/board-read-views.js");
@@ -526,9 +528,18 @@ class GatewayToolExecutor {
526
528
  wikiPublishAdapter = null;
527
529
  obsidianVaultPath = null;
528
530
  obsidianVaultName = null;
531
+ obsidianVaultVerified = false;
532
+ wikiReadCoverage = new Map();
533
+ wikiSourceCoverage = new Map();
529
534
  setObsidianVaultPath(vaultPath, vaultName) {
530
535
  this.obsidianVaultPath = vaultPath;
531
536
  this.obsidianVaultName = vaultName ?? null;
537
+ this.obsidianVaultVerified = false;
538
+ }
539
+ releaseWikiAttemptCoverage(workorderAttemptId) {
540
+ if (Number.isSafeInteger(workorderAttemptId) && workorderAttemptId > 0) {
541
+ this.clearWikiAttemptCoverage(workorderAttemptId);
542
+ }
532
543
  }
533
544
  taskLedger = null;
534
545
  ownerEventEffectLedger = null;
@@ -2616,12 +2627,50 @@ class GatewayToolExecutor {
2616
2627
  if (!pagesInput || !Array.isArray(pagesInput)) {
2617
2628
  throw new types_js_1.AgentError('wiki_publish requires pages array', 'TOOL_ERROR', undefined, false);
2618
2629
  }
2630
+ const wikiAuthority = this.getExecutionState().wikiTaskRange;
2631
+ if (wikiAuthority) {
2632
+ if (!this.obsidianVaultPath) {
2633
+ throw new types_js_1.AgentError('Wiki vault path not configured', 'TOOL_ERROR', undefined, false);
2634
+ }
2635
+ if (wikiAuthority.ownerDate === null) {
2636
+ throw new types_js_1.AgentError('wiki_publish is unavailable for legacy input without a host-issued ownerDate', 'WORKORDER_SUPERSEDED', undefined, false);
2637
+ }
2638
+ try {
2639
+ (0, wiki_read_js_1.assertWikiWorkorderPublish)({
2640
+ root: this.obsidianVaultPath,
2641
+ ownerDate: wikiAuthority.ownerDate,
2642
+ pages: pagesInput,
2643
+ });
2644
+ const attemptId = this.getExecutionState().workorderAttemptId;
2645
+ if (!Number.isSafeInteger(attemptId) || attemptId < 1) {
2646
+ throw new Error('wiki_publish requires a host-issued workorder attempt');
2647
+ }
2648
+ for (const page of pagesInput) {
2649
+ const path = (0, wiki_read_js_1.assertAllowedWikiWorkorderPath)(page.path, wikiAuthority.ownerDate);
2650
+ const coverage = this.wikiReadCoverage.get(this.wikiReadCoverageKey(attemptId, path));
2651
+ if (!coverage?.complete ||
2652
+ coverage.contentVersion !== page.expectedContentVersion) {
2653
+ throw new Error(`wiki_publish page ${path} requires a complete contiguous wiki_read at the expected content version`);
2654
+ }
2655
+ }
2656
+ }
2657
+ catch (error) {
2658
+ throw new types_js_1.AgentError(error instanceof Error ? error.message : 'Wiki publication authority failed', 'TOOL_ERROR', undefined, false);
2659
+ }
2660
+ }
2661
+ const publishPages = wikiAuthority
2662
+ ? pagesInput
2663
+ : pagesInput.map(({ expectedContentVersion: _ignored, ...page }) => page);
2619
2664
  const adapter = this.wikiPublishAdapter ??
2620
2665
  (0, wiki_publish_adapter_js_1.createWikiPublishAdapter)({
2621
2666
  publisher: this.wikiPublisher,
2622
2667
  });
2623
2668
  try {
2624
- const publishResult = adapter.publish({ pages: pagesInput });
2669
+ const publishResult = adapter.publish({ pages: publishPages });
2670
+ if (wikiAuthority) {
2671
+ const attemptId = this.getExecutionState().workorderAttemptId;
2672
+ this.clearWikiAttemptCoverage(attemptId);
2673
+ }
2625
2674
  return {
2626
2675
  success: true,
2627
2676
  message: `Wiki published: ${publishResult.pagesPublished} pages`,
@@ -2632,6 +2681,66 @@ class GatewayToolExecutor {
2632
2681
  throw new types_js_1.AgentError(error instanceof Error ? error.message : 'Wiki publish failed', 'TOOL_ERROR', undefined, false);
2633
2682
  }
2634
2683
  }
2684
+ case 'wiki_read': {
2685
+ const wikiAuthority = this.getExecutionState().wikiTaskRange;
2686
+ if (!wikiAuthority || wikiAuthority.ownerDate === null) {
2687
+ throw new types_js_1.AgentError('wiki_read requires a host-issued wiki workorder ownerDate', 'WORKORDER_SUPERSEDED', undefined, false);
2688
+ }
2689
+ if (!this.obsidianVaultPath) {
2690
+ throw new types_js_1.AgentError('Wiki vault path not configured', 'TOOL_ERROR', undefined, false);
2691
+ }
2692
+ try {
2693
+ const attemptId = this.getExecutionState().workorderAttemptId;
2694
+ if (!Number.isSafeInteger(attemptId) || attemptId < 1) {
2695
+ throw new Error('wiki_read requires a host-issued workorder attempt');
2696
+ }
2697
+ const readInput = input;
2698
+ const requestedOffset = readInput.content_offset ?? 0;
2699
+ if (!Number.isSafeInteger(requestedOffset) || requestedOffset < 0) {
2700
+ throw new Error('wiki_read content_offset must be a non-negative safe integer');
2701
+ }
2702
+ const paths = Array.isArray(readInput.paths) ? readInput.paths : [];
2703
+ for (const rawPath of paths) {
2704
+ const path = (0, wiki_read_js_1.assertAllowedWikiWorkorderPath)(rawPath, wikiAuthority.ownerDate);
2705
+ const key = this.wikiReadCoverageKey(attemptId, path);
2706
+ const prior = this.wikiReadCoverage.get(key);
2707
+ if (requestedOffset === 0) {
2708
+ this.wikiReadCoverage.delete(key);
2709
+ }
2710
+ else if (!prior || prior.complete || prior.nextOffset !== requestedOffset) {
2711
+ throw new Error(`wiki_read page ${path} must continue from the host-issued nextContentOffset`);
2712
+ }
2713
+ }
2714
+ const result = (0, wiki_read_js_1.readWikiPages)({
2715
+ root: this.obsidianVaultPath,
2716
+ ownerDate: wikiAuthority.ownerDate,
2717
+ paths: readInput.paths,
2718
+ contentOffset: readInput.content_offset,
2719
+ contentLimit: readInput.content_limit,
2720
+ });
2721
+ for (const page of result.pages) {
2722
+ const key = this.wikiReadCoverageKey(attemptId, page.path);
2723
+ const prior = this.wikiReadCoverage.get(key);
2724
+ if (requestedOffset > 0 &&
2725
+ prior?.contentVersion !== page.contentVersion) {
2726
+ this.wikiReadCoverage.delete(key);
2727
+ throw new Error(`wiki_read page ${page.path} changed; restart from offset 0`);
2728
+ }
2729
+ this.wikiReadCoverage.set(key, {
2730
+ contentVersion: page.contentVersion,
2731
+ nextOffset: page.nextContentOffset,
2732
+ complete: page.nextContentOffset === null,
2733
+ });
2734
+ }
2735
+ return {
2736
+ success: true,
2737
+ ...result,
2738
+ };
2739
+ }
2740
+ catch (error) {
2741
+ throw new types_js_1.AgentError(error instanceof Error ? error.message : 'Wiki read failed', 'TOOL_ERROR', undefined, false);
2742
+ }
2743
+ }
2635
2744
  // Kagemusha query tools — progressive business data exploration
2636
2745
  case 'kagemusha_overview': {
2637
2746
  const { getOverview } = await import('../connectors/kagemusha/query-tools.js');
@@ -2761,6 +2870,7 @@ class GatewayToolExecutor {
2761
2870
  // superseded signal it was before the progressive views.
2762
2871
  const temporalContext = this.getExecutionState().temporalWorkContext;
2763
2872
  const wikiTaskRange = this.getExecutionState().wikiTaskRange;
2873
+ let taskListInput = input;
2764
2874
  if (wikiTaskRange) {
2765
2875
  const taskInput = input !== null && typeof input === 'object' && !Array.isArray(input)
2766
2876
  ? input
@@ -2768,11 +2878,38 @@ class GatewayToolExecutor {
2768
2878
  if (wikiTaskRange.updatedSince === null || wikiTaskRange.updatedBefore === null) {
2769
2879
  throw new types_js_1.AgentError('Wiki task_list is unavailable for legacy input without a host-issued range', 'WORKORDER_SUPERSEDED', undefined, false);
2770
2880
  }
2771
- if ((taskInput.view !== undefined && taskInput.view !== 'items') ||
2772
- taskInput.updated_since !== wikiTaskRange.updatedSince ||
2773
- taskInput.updated_before !== wikiTaskRange.updatedBefore) {
2774
- throw new types_js_1.AgentError('Wiki task_list requires view items and the exact host-issued updated_since/updated_before range', 'TOOL_ERROR', undefined, false);
2881
+ if (taskInput.view !== undefined && taskInput.view !== 'items') {
2882
+ throw new types_js_1.AgentError('Wiki task_list requires view items', 'TOOL_ERROR', undefined, false);
2883
+ }
2884
+ const allowedFields = new Set([
2885
+ 'view',
2886
+ 'cursor',
2887
+ 'limit',
2888
+ 'updated_since',
2889
+ 'updated_before',
2890
+ ]);
2891
+ const narrowingFields = Object.keys(taskInput).filter((field) => !allowedFields.has(field));
2892
+ if (narrowingFields.length > 0) {
2893
+ throw new types_js_1.AgentError(`Wiki task_list cannot narrow the host-issued range with: ${narrowingFields.join(', ')}`, 'TOOL_ERROR', undefined, false);
2894
+ }
2895
+ if ((taskInput.updated_since !== undefined &&
2896
+ taskInput.updated_since !== wikiTaskRange.updatedSince) ||
2897
+ (taskInput.updated_before !== undefined &&
2898
+ taskInput.updated_before !== wikiTaskRange.updatedBefore)) {
2899
+ throw new types_js_1.AgentError('Wiki task_list parameters contradict the host-issued updated_since/updated_before range', 'TOOL_ERROR', undefined, false);
2900
+ }
2901
+ const normalizedInput = { ...taskInput };
2902
+ if (normalizedInput.cursor === null) {
2903
+ delete normalizedInput.cursor;
2775
2904
  }
2905
+ taskListInput = {
2906
+ ...normalizedInput,
2907
+ view: 'items',
2908
+ include_terminal: true,
2909
+ order: 'updated',
2910
+ updated_since: wikiTaskRange.updatedSince,
2911
+ updated_before: wikiTaskRange.updatedBefore,
2912
+ };
2776
2913
  }
2777
2914
  let boundTask;
2778
2915
  if (temporalContext) {
@@ -2782,10 +2919,17 @@ class GatewayToolExecutor {
2782
2919
  }
2783
2920
  boundTask = found;
2784
2921
  }
2785
- return (0, task_list_views_js_1.runTaskListView)(input, {
2922
+ const taskListResult = (0, task_list_views_js_1.runTaskListView)(taskListInput, {
2786
2923
  ledger: this.taskLedger,
2787
2924
  boundTask,
2788
2925
  });
2926
+ if (wikiTaskRange && taskListResult.success === true) {
2927
+ const nextCursor = taskListResult.nextCursor;
2928
+ if (nextCursor === null || nextCursor === undefined) {
2929
+ this.markWikiSourceCoverage('task_list');
2930
+ }
2931
+ }
2932
+ return taskListResult;
2789
2933
  }
2790
2934
  case 'task_external_correlation': {
2791
2935
  if (!this.taskLedger) {
@@ -3197,7 +3341,30 @@ class GatewayToolExecutor {
3197
3341
  if (!reason || !scope) {
3198
3342
  throw new types_js_1.AgentError('contract_no_update requires both reason and scope', 'TOOL_ERROR', undefined, false);
3199
3343
  }
3200
- return { success: true, note: this.taskLedger.recordNoUpdate(scope, reason) };
3344
+ const wikiAuthority = this.getExecutionState().wikiTaskRange;
3345
+ if (wikiAuthority) {
3346
+ if (wikiAuthority.noUpdateScope === null) {
3347
+ throw new types_js_1.AgentError('contract_no_update is unavailable for legacy wiki input without a host-issued scope', 'WORKORDER_SUPERSEDED', undefined, false);
3348
+ }
3349
+ if (scope !== wikiAuthority.noUpdateScope) {
3350
+ throw new types_js_1.AgentError('Wiki contract_no_update scope must match the exact host-issued scope', 'TOOL_ERROR', undefined, false);
3351
+ }
3352
+ const attemptId = this.getExecutionState().workorderAttemptId;
3353
+ if (!Number.isSafeInteger(attemptId) || attemptId < 1) {
3354
+ throw new types_js_1.AgentError('Wiki contract_no_update requires a host-issued workorder attempt', 'WORKORDER_SUPERSEDED', undefined, false);
3355
+ }
3356
+ try {
3357
+ this.assertWikiNoUpdateCoverage(attemptId, wikiAuthority);
3358
+ }
3359
+ catch (error) {
3360
+ throw new types_js_1.AgentError(error instanceof Error ? error.message : 'Wiki source coverage is incomplete', 'TOOL_ERROR', undefined, false);
3361
+ }
3362
+ }
3363
+ const note = this.taskLedger.recordNoUpdate(scope, reason);
3364
+ if (wikiAuthority) {
3365
+ this.clearWikiAttemptCoverage(this.getExecutionState().workorderAttemptId);
3366
+ }
3367
+ return { success: true, note };
3201
3368
  }
3202
3369
  case 'agent_notices': {
3203
3370
  const rawLimit = Number(input.limit);
@@ -3919,20 +4086,33 @@ class GatewayToolExecutor {
3919
4086
  error: 'Wiki vault path not configured',
3920
4087
  };
3921
4088
  }
4089
+ if (this.obsidianVaultName && !this.obsidianVaultVerified) {
4090
+ try {
4091
+ const { stdout } = await execFileAsync('obsidian', (0, obsidian_cli_args_js_1.buildObsidianCliArgs)('vault', undefined, this.obsidianVaultName), {
4092
+ timeout: 15000,
4093
+ encoding: 'utf-8',
4094
+ maxBuffer: 1024 * 1024,
4095
+ });
4096
+ const configuredPath = (0, fs_1.realpathSync)(this.obsidianVaultPath);
4097
+ const selectedPath = (0, fs_1.realpathSync)((0, obsidian_cli_args_js_1.parseObsidianVaultPath)(stdout));
4098
+ if (selectedPath !== configuredPath) {
4099
+ throw new Error(`Obsidian CLI selected ${selectedPath}, expected configured wiki vault ${configuredPath}`);
4100
+ }
4101
+ this.obsidianVaultVerified = true;
4102
+ }
4103
+ catch (error) {
4104
+ throw new types_js_1.AgentError(error instanceof Error ? error.message : 'Obsidian vault target verification failed', 'TOOL_ERROR', undefined, false);
4105
+ }
4106
+ }
3922
4107
  // Obsidian CLI syntax: obsidian <command> key=value ... [flags]
3923
4108
  // Without vault=<name> the CLI targets the FOCUSED vault, so wiki writes
3924
4109
  // could land in whatever vault the owner has open. Pin it when configured.
3925
- const cliArgs = [command];
3926
- if (this.obsidianVaultName) {
3927
- cliArgs.push(`vault=${this.obsidianVaultName}`);
4110
+ let cliArgs;
4111
+ try {
4112
+ cliArgs = (0, obsidian_cli_args_js_1.buildObsidianCliArgs)(command, args, this.obsidianVaultName);
3928
4113
  }
3929
- for (const [key, value] of Object.entries(args || {})) {
3930
- if (value === 'true' && ['silent', 'overwrite', 'total'].includes(key)) {
3931
- cliArgs.push(key);
3932
- }
3933
- else {
3934
- cliArgs.push(`${key}=${value}`);
3935
- }
4114
+ catch (error) {
4115
+ throw new types_js_1.AgentError(error instanceof Error ? error.message : 'Invalid Obsidian CLI arguments', 'TOOL_ERROR', undefined, false);
3936
4116
  }
3937
4117
  try {
3938
4118
  const { stdout } = await execFileAsync('obsidian', cliArgs, {
@@ -3959,6 +4139,47 @@ class GatewayToolExecutor {
3959
4139
  };
3960
4140
  }
3961
4141
  }
4142
+ wikiReadCoverageKey(attemptId, path) {
4143
+ return `${attemptId}:${path}`;
4144
+ }
4145
+ markWikiSourceCoverage(source) {
4146
+ const attemptId = this.getExecutionState().workorderAttemptId;
4147
+ if (!Number.isSafeInteger(attemptId) || attemptId < 1) {
4148
+ return;
4149
+ }
4150
+ const current = this.wikiSourceCoverage.get(attemptId) ?? {
4151
+ contextCompileComplete: false,
4152
+ taskListComplete: false,
4153
+ };
4154
+ this.wikiSourceCoverage.set(attemptId, {
4155
+ contextCompileComplete: current.contextCompileComplete || source === 'context_compile',
4156
+ taskListComplete: current.taskListComplete || source === 'task_list',
4157
+ });
4158
+ }
4159
+ assertWikiNoUpdateCoverage(attemptId, authority) {
4160
+ if (authority.ownerDate === null) {
4161
+ throw new Error('Wiki no-update coverage requires a host-issued ownerDate');
4162
+ }
4163
+ const sources = this.wikiSourceCoverage.get(attemptId);
4164
+ if (!sources?.contextCompileComplete || !sources.taskListComplete) {
4165
+ throw new Error('Wiki contract_no_update requires completed context_compile and all bounded task_list pages');
4166
+ }
4167
+ for (const path of ['Home.md', `daily/${authority.ownerDate}.md`]) {
4168
+ const read = this.wikiReadCoverage.get(this.wikiReadCoverageKey(attemptId, path));
4169
+ if (!read?.complete) {
4170
+ throw new Error('Wiki contract_no_update requires complete wiki_read coverage for Home.md and the bound daily page');
4171
+ }
4172
+ }
4173
+ }
4174
+ clearWikiAttemptCoverage(attemptId) {
4175
+ this.wikiSourceCoverage.delete(attemptId);
4176
+ const prefix = `${attemptId}:`;
4177
+ for (const key of this.wikiReadCoverage.keys()) {
4178
+ if (key.startsWith(prefix)) {
4179
+ this.wikiReadCoverage.delete(key);
4180
+ }
4181
+ }
4182
+ }
3962
4183
  async executeCodeAct(input) {
3963
4184
  const { CodeActSandbox, CodeActToolPolicyValidationError, CODE_ACT_MUTATION_COMMITTED_AFTER_ABORT, CODE_ACT_MUTATION_OUTCOME_UNKNOWN, HostBridge, projectCodeActToolPolicy, } = await import('./code-act/index.js');
3964
4185
  const sandbox = new CodeActSandbox();
@@ -4254,6 +4475,36 @@ class GatewayToolExecutor {
4254
4475
  let effectiveInput = temporalContext
4255
4476
  ? { ...input, task: bindTemporalContextPacketTask(temporalContext, input.task) }
4256
4477
  : input;
4478
+ const wikiAuthority = ctx.wikiTaskRange;
4479
+ if (wikiAuthority) {
4480
+ if (wikiAuthority.rangeStartMs === null ||
4481
+ wikiAuthority.rangeEndMs === null ||
4482
+ wikiAuthority.connectors === null) {
4483
+ throw new Error('context_compile is unavailable for legacy wiki input');
4484
+ }
4485
+ const providedRange = input.range;
4486
+ if (providedRange !== undefined &&
4487
+ (providedRange.start_ms !== wikiAuthority.rangeStartMs ||
4488
+ providedRange.end_ms !== wikiAuthority.rangeEndMs)) {
4489
+ throw new Error('context_compile range contradicts the host-issued wiki range');
4490
+ }
4491
+ if (input.connectors !== undefined) {
4492
+ const providedConnectors = [...input.connectors].sort();
4493
+ const allowedConnectors = [...wikiAuthority.connectors].sort();
4494
+ if (providedConnectors.length !== allowedConnectors.length ||
4495
+ providedConnectors.some((connector, index) => connector !== allowedConnectors[index])) {
4496
+ throw new Error('context_compile connectors contradict the host-issued wiki connector scope');
4497
+ }
4498
+ }
4499
+ effectiveInput = {
4500
+ ...effectiveInput,
4501
+ range: {
4502
+ start_ms: wikiAuthority.rangeStartMs,
4503
+ end_ms: wikiAuthority.rangeEndMs,
4504
+ },
4505
+ connectors: [...wikiAuthority.connectors],
4506
+ };
4507
+ }
4257
4508
  // (d)-by-construction (S2 measurement: 94% of reconcile rejections were
4258
4509
  // packets carrying only recalled memories). The HOST knows the bound
4259
4510
  // source - the task row holds it raw; the context carries only hashes -
@@ -4331,6 +4582,9 @@ class GatewayToolExecutor {
4331
4582
  !temporalPacketRawSourcesWithinBoundSource(temporalContext, result.packet.source_refs, reviewWindow)) {
4332
4583
  throw new Error('context_compile packet exceeds the active temporal task source');
4333
4584
  }
4585
+ if (wikiAuthority) {
4586
+ this.markWikiSourceCoverage('context_compile');
4587
+ }
4334
4588
  return {
4335
4589
  success: true,
4336
4590
  packet: result.packet,