@nextclaw/x-harness 0.1.1 → 0.2.0

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 (79) hide show
  1. package/LICENSE +231 -6
  2. package/README.md +15 -1
  3. package/SOURCE.md +9 -0
  4. package/cli/main.js +232 -200
  5. package/dist/api.d.ts +56 -2
  6. package/dist/api.js +130 -79
  7. package/dist/boundary.js +1 -0
  8. package/dist/browser-common.d.ts +11 -0
  9. package/dist/browser-common.js +34 -18
  10. package/dist/browser-dom-capabilities.d.ts +7 -2
  11. package/dist/browser-dom-capabilities.js +123 -102
  12. package/dist/browser-dom-dependencies.js +1 -0
  13. package/dist/browser-dom-editing.d.ts +20 -2
  14. package/dist/browser-dom-editing.js +148 -116
  15. package/dist/browser-dom-navigation.d.ts +11 -2
  16. package/dist/browser-dom-navigation.js +66 -50
  17. package/dist/browser-dom-submission.d.ts +6 -1
  18. package/dist/browser-dom-submission.js +37 -18
  19. package/dist/browser-dom.d.ts +13 -1
  20. package/dist/browser-dom.js +113 -66
  21. package/dist/browser-native.d.ts +14 -1
  22. package/dist/browser-native.js +161 -128
  23. package/dist/creator-native.d.ts +3 -0
  24. package/dist/creator-native.js +113 -0
  25. package/dist/diagnostics.d.ts +11 -0
  26. package/dist/diagnostics.js +10 -0
  27. package/dist/growth-batch.d.ts +6 -3
  28. package/dist/growth-batch.js +157 -135
  29. package/dist/growth-dependencies.js +1 -0
  30. package/dist/growth-execution.d.ts +8 -2
  31. package/dist/growth-execution.js +68 -37
  32. package/dist/growth-parallel.d.ts +6 -2
  33. package/dist/growth-parallel.js +78 -47
  34. package/dist/growth-plan.d.ts +8 -3
  35. package/dist/growth-plan.js +45 -23
  36. package/dist/growth-policy.d.ts +11 -2
  37. package/dist/growth-policy.js +80 -53
  38. package/dist/growth-publishing.d.ts +7 -2
  39. package/dist/growth-publishing.js +72 -46
  40. package/dist/growth-reading.d.ts +6 -2
  41. package/dist/growth-reading.js +47 -25
  42. package/dist/growth-recovery.d.ts +7 -4
  43. package/dist/growth-recovery.js +177 -148
  44. package/dist/growth-state.d.ts +10 -2
  45. package/dist/growth-state.js +67 -30
  46. package/dist/growth-tasks.d.ts +19 -16
  47. package/dist/growth-tasks.js +153 -90
  48. package/dist/growth.d.ts +25 -1
  49. package/dist/growth.js +207 -124
  50. package/dist/platform-capabilities.d.ts +6 -2
  51. package/dist/platform-capabilities.js +18 -8
  52. package/dist/platform-collections.d.ts +10 -3
  53. package/dist/platform-collections.js +62 -35
  54. package/dist/platform-dependencies.js +1 -0
  55. package/dist/platform-diagnostics.d.ts +7 -2
  56. package/dist/platform-diagnostics.js +31 -19
  57. package/dist/platform-observation.d.ts +10 -2
  58. package/dist/platform-observation.js +44 -23
  59. package/dist/platform-targets.d.ts +10 -3
  60. package/dist/platform-targets.js +39 -14
  61. package/dist/platform-writing.d.ts +10 -2
  62. package/dist/platform-writing.js +63 -32
  63. package/dist/platform.d.ts +22 -1
  64. package/dist/platform.js +120 -52
  65. package/dist/readers-ax-host.d.ts +11 -1
  66. package/dist/readers-ax-host.js +108 -99
  67. package/dist/readers-ax-program.js +5 -0
  68. package/dist/readers-ax-tree.js +1 -0
  69. package/dist/readers-cli.js +79 -0
  70. package/dist/readers.js +9 -0
  71. package/dist/round-state.js +18 -1
  72. package/dist/store.d.ts +22 -4
  73. package/dist/store.js +173 -151
  74. package/dist/types.js +1 -0
  75. package/generated/observation.mjs +1 -1
  76. package/package.json +23 -9
  77. package/skill/SKILL.md +2 -2
  78. package/skill/references/reads.md +1 -1
  79. package/skill/references/setup.md +1 -1
@@ -1,30 +1,32 @@
1
1
  import { xURL } from "./platform-rules.js";
2
2
  import { kinds, followWindows, operations, same } from "./growth-constants.js";
3
- export function createGrowthPolicy(dependencies) {
4
- const { executor, clock, evidenceExists, platform, store } = dependencies;
5
- // Manual user authorization applies only to the named executor and action kind.
6
- const authorizedWindow = (state, kind) => {
7
- const permit = state.executions?.[executor?.id]?.windowAuthorization;
8
- const now = clock();
9
- return Boolean(permit &&
10
- permit.kind === kind &&
11
- permit.executorId === executor?.id &&
12
- permit.userAuthorized === true &&
13
- evidenceExists(permit.evidenceRef) &&
14
- Date.parse(permit.startsAt) <= now &&
15
- now < Date.parse(permit.expiresAt));
16
- };
17
- const scope = (state) => executor?.scopeKinds ?? state.scopeKinds ?? kinds;
18
- const project = (state) => executor
19
- ? {
20
- ...state,
21
- scopeKinds: executor.scopeKinds,
22
- scopeEvidenceRef: executor.evidenceRef,
23
- status: state.executions?.[executor.id]?.status ?? "RUNNING",
24
- stop: state.executions?.[executor.id]?.stop,
25
- }
26
- : state;
27
- function capability(operation, input = {}) {
3
+ export class GrowthPolicy {
4
+ #executor;
5
+ #clock;
6
+ #evidenceExists;
7
+ #platform;
8
+ #store;
9
+ authorizedWindow;
10
+ scope;
11
+ project;
12
+ capability;
13
+ waitBudget;
14
+ guardInput;
15
+ constructor(dependencies) {
16
+ const { executor, clock, evidenceExists, platform, store } = dependencies;
17
+ this.#executor = executor;
18
+ this.#clock = clock;
19
+ this.#evidenceExists = evidenceExists;
20
+ this.#platform = platform;
21
+ this.#store = store;
22
+ this.authorizedWindow = this.handleAuthorizedWindow.bind(this);
23
+ this.scope = this.handleScope.bind(this);
24
+ this.project = this.handleProject.bind(this);
25
+ this.capability = this.handleCapability.bind(this);
26
+ this.waitBudget = this.handleWaitBudget.bind(this);
27
+ this.guardInput = this.handleGuardInput.bind(this);
28
+ }
29
+ handleCapability(operation, input = {}) {
28
30
  const kind = input.kind ??
29
31
  {
30
32
  followOne: "follow",
@@ -44,17 +46,18 @@ export function createGrowthPolicy(dependencies) {
44
46
  ];
45
47
  if (kind === "follow")
46
48
  required.push("readPerson");
47
- const missing = required.filter((name) => typeof platform?.[name] !== "function");
48
- const storage = ["load", "save", "withLock", "recordProof"].filter((name) => typeof store?.[name] !==
49
+ const missing = required.filter((name) => typeof this.#platform?.[name] !== "function");
50
+ const storage = ["load", "save", "withLock", "recordProof"].filter((name) => typeof this.#store?.[name] !==
49
51
  "function");
50
- if (operation === "followBatch" && typeof store?.transaction !== "function")
52
+ if (operation === "followBatch" &&
53
+ typeof this.#store?.transaction !== "function")
51
54
  storage.push("transaction");
52
55
  if (kind === "original" &&
53
56
  operation !== "read" &&
54
- typeof store?.fingerprint !== "function")
57
+ typeof this.#store?.fingerprint !== "function")
55
58
  storage.push("fingerprint");
56
- const lower = typeof platform?.preflight === "function"
57
- ? platform.preflight({
59
+ const lower = typeof this.#platform?.preflight === "function"
60
+ ? this.#platform.preflight({
58
61
  operation: operation === "read" ? "read" : operations[kind],
59
62
  input,
60
63
  })
@@ -73,8 +76,8 @@ export function createGrowthPolicy(dependencies) {
73
76
  : lower.owner,
74
77
  };
75
78
  }
76
- function waitBudget(state) {
77
- const now = clock();
79
+ handleWaitBudget(state) {
80
+ const now = this.#clock();
78
81
  const hour = Number(new Intl.DateTimeFormat("en-GB", {
79
82
  timeZone: "Asia/Shanghai",
80
83
  hour: "2-digit",
@@ -89,7 +92,7 @@ export function createGrowthPolicy(dependencies) {
89
92
  if (!coverage?.complete ||
90
93
  !Number.isFinite(Date.parse(coverage.since)) ||
91
94
  Date.parse(coverage.since) > now - 3600000 ||
92
- !evidenceExists(coverage.evidenceRef))
95
+ !this.#evidenceExists(coverage.evidenceRef))
93
96
  return {
94
97
  status: "needsDecision",
95
98
  reason: "shared-attempt-history-incomplete",
@@ -118,14 +121,15 @@ export function createGrowthPolicy(dependencies) {
118
121
  };
119
122
  return null;
120
123
  }
121
- function guardInput(state, action, independentExecution = undefined) {
122
- if (!independentExecution && !scope(state).includes(action.kind))
124
+ handleGuardInput(state, action, independentExecution = undefined) {
125
+ const executor = this.#executor;
126
+ if (!independentExecution && !this.handleScope(state).includes(action.kind))
123
127
  return "kind-outside-round-scope";
124
128
  if (independentExecution) {
125
129
  if (action.kind !== "original" ||
126
130
  !independentExecution.tabId ||
127
- !evidenceExists(independentExecution.evidenceRef) ||
128
- !evidenceExists(independentExecution.scopeEvidenceRef))
131
+ !this.#evidenceExists(independentExecution.evidenceRef) ||
132
+ !this.#evidenceExists(independentExecution.scopeEvidenceRef))
129
133
  return "independent-execution-proof-missing";
130
134
  }
131
135
  else if (!executor &&
@@ -133,49 +137,72 @@ export function createGrowthPolicy(dependencies) {
133
137
  return "exclusive-account-execution-unconfirmed";
134
138
  if (!same(state.accountHandle, action.accountHandle))
135
139
  return "account-identity-mismatch";
136
- if (!evidenceExists(action.accountEvidenceRef) ||
140
+ if (!this.#evidenceExists(action.accountEvidenceRef) ||
137
141
  xURL(action.accountURL).pathname.toLowerCase() !==
138
142
  `/${state.accountHandle}`.toLowerCase())
139
143
  return "own-profile-proof-missing";
140
144
  if (!action.quality?.approved ||
141
- !evidenceExists(action.quality.evidenceRef))
145
+ !this.#evidenceExists(action.quality.evidenceRef))
142
146
  return "quality-review-required";
143
147
  if (action.kind === "follow") {
144
148
  if (!same(action.quality.handle, action.handle) ||
145
149
  action.quality.blue !== true ||
146
150
  action.quality.chinese !== true)
147
151
  return "follow-qualification-required";
148
- if (!evidenceExists(action.evidenceRef))
152
+ if (!this.#evidenceExists(action.evidenceRef))
149
153
  return "person-observation-proof-missing";
150
154
  }
151
155
  else {
152
156
  if (!action.text || action.quality.approvedText !== action.text)
153
157
  return "exact-text-quality-review-required";
154
158
  if (action.kind === "original" &&
155
- (!action.draftPath || !evidenceExists(action.draftPath)))
159
+ (!action.draftPath || !this.#evidenceExists(action.draftPath)))
156
160
  return "draft-source-missing";
157
161
  if (action.kind === "original" &&
158
162
  (!action.draftPath.endsWith(".post.md") ||
159
163
  action.quality.draftFingerprint !==
160
- store.fingerprint(action.draftPath)))
164
+ this.#store.fingerprint(action.draftPath)))
161
165
  return "draft-changed-since-quality-review";
162
166
  if (action.kind === "original" &&
163
- (!evidenceExists(action.composeEvidenceRef) ||
167
+ (!this.#evidenceExists(action.composeEvidenceRef) ||
164
168
  xURL(action.composeURL).pathname !== "/compose/post"))
165
169
  return "observed-composer-proof-missing";
166
170
  if (action.kind === "reply" &&
167
171
  (!/^\d+$/.test(action.postId ?? "") ||
168
- !evidenceExists(action.evidenceRef)))
172
+ !this.#evidenceExists(action.evidenceRef)))
169
173
  return "reply-target-proof-missing";
170
174
  }
171
175
  return null;
172
176
  }
173
- return {
174
- authorizedWindow,
175
- scope,
176
- project,
177
- capability,
178
- waitBudget,
179
- guardInput,
180
- };
177
+ handleAuthorizedWindow(state, kind) {
178
+ const executor = this.#executor;
179
+ const permit = state.executions?.[executor?.id]?.windowAuthorization;
180
+ const now = this.#clock();
181
+ return Boolean(permit &&
182
+ permit.kind === kind &&
183
+ permit.executorId === executor?.id &&
184
+ permit.userAuthorized === true &&
185
+ this.#evidenceExists(permit.evidenceRef) &&
186
+ Date.parse(permit.startsAt) <= now &&
187
+ now < Date.parse(permit.expiresAt));
188
+ }
189
+ handleScope(state) {
190
+ const executor = this.#executor;
191
+ return executor?.scopeKinds ?? state.scopeKinds ?? kinds;
192
+ }
193
+ handleProject(state) {
194
+ const executor = this.#executor;
195
+ return executor
196
+ ? {
197
+ ...state,
198
+ scopeKinds: executor.scopeKinds,
199
+ scopeEvidenceRef: executor.evidenceRef,
200
+ status: state.executions?.[executor.id]?.status ?? "RUNNING",
201
+ stop: state.executions?.[executor.id]?.stop,
202
+ }
203
+ : state;
204
+ }
205
+ }
206
+ export function createGrowthPolicy(...args) {
207
+ return new GrowthPolicy(...args);
181
208
  }
@@ -1,5 +1,10 @@
1
1
  import type { GrowthDependencies } from "./growth-dependencies.js";
2
- export declare function createIndependentPublishing(dependencies: Pick<GrowthDependencies, "clock" | "evidenceExists" | "capability" | "store" | "load" | "guardInput" | "save" | "platform" | "stamp" | "settleProof">): {
2
+ export declare class IndependentPublishing {
3
+ #private;
3
4
  publishIndependent: ({ taskId, draftPath, preparedAction, independentExecution, }: any) => Promise<any>;
4
5
  prepareActionsForCall: (taskId: string, actions?: any[]) => Promise<void>;
5
- };
6
+ constructor(dependencies: Pick<GrowthDependencies, "clock" | "evidenceExists" | "capability" | "store" | "load" | "guardInput" | "save" | "platform" | "stamp" | "settleProof">);
7
+ private handlePublishIndependent;
8
+ private handlePrepareActionsForCall;
9
+ }
10
+ export declare function createIndependentPublishing(...args: ConstructorParameters<typeof IndependentPublishing>): IndependentPublishing;
@@ -1,30 +1,55 @@
1
1
  import { kinds, same } from "./growth-constants.js";
2
- export function createIndependentPublishing(dependencies) {
3
- const { clock, evidenceExists, capability, store, load, guardInput, save, platform, stamp, settleProof, } = dependencies;
4
- // Independent tabs share only short ledger transactions, never a browser-operation lock.
5
- async function publishIndependent({ taskId, draftPath, preparedAction, independentExecution, }) {
2
+ export class IndependentPublishing {
3
+ #clock;
4
+ #evidenceExists;
5
+ #capability;
6
+ #store;
7
+ #load;
8
+ #guardInput;
9
+ #save;
10
+ #platform;
11
+ #stamp;
12
+ #settleProof;
13
+ publishIndependent;
14
+ prepareActionsForCall;
15
+ constructor(dependencies) {
16
+ const { clock, evidenceExists, capability, store, load, guardInput, save, platform, stamp, settleProof, } = dependencies;
17
+ this.#clock = clock;
18
+ this.#evidenceExists = evidenceExists;
19
+ this.#capability = capability;
20
+ this.#store = store;
21
+ this.#load = load;
22
+ this.#guardInput = guardInput;
23
+ this.#save = save;
24
+ this.#platform = platform;
25
+ this.#stamp = stamp;
26
+ this.#settleProof = settleProof;
27
+ this.publishIndependent = this.handlePublishIndependent.bind(this);
28
+ this.prepareActionsForCall = this.handlePrepareActionsForCall.bind(this);
29
+ }
30
+ async handlePublishIndependent({ taskId, draftPath, preparedAction, independentExecution, }) {
6
31
  let action, token, submissionStarted = false;
7
32
  const inWindow = () => {
8
33
  const hour = Number(new Intl.DateTimeFormat("en-GB", {
9
34
  timeZone: "Asia/Shanghai",
10
35
  hour: "2-digit",
11
36
  hourCycle: "h23",
12
- }).format(clock()));
37
+ }).format(this.#clock()));
13
38
  const permit = independentExecution?.windowAuthorization;
14
39
  return ((hour >= 7 && hour < 22) ||
15
40
  Boolean(permit?.userAuthorized === true &&
16
41
  permit.actionId === (action?.id ?? preparedAction?.id) &&
17
- evidenceExists(permit.evidenceRef) &&
18
- clock() < Date.parse(permit.expiresAt)));
42
+ this.#evidenceExists(permit.evidenceRef) &&
43
+ this.#clock() < Date.parse(permit.expiresAt)));
19
44
  };
20
45
  try {
21
- const caps = capability("submitPost", preparedAction ?? {});
46
+ const caps = this.#capability("submitPost", preparedAction ?? {});
22
47
  if (!caps.covered)
23
48
  return { status: "needsCapability", ...caps };
24
49
  if (!inWindow())
25
50
  throw new Error("outside-contract-write-window");
26
- await store.withLock(async () => {
27
- const snapshot = await load(taskId), state = structuredClone(snapshot.state);
51
+ await this.#store.withLock(async () => {
52
+ const snapshot = await this.#load(taskId), state = structuredClone(snapshot.state);
28
53
  state.actions ??= [];
29
54
  action = state.actions.find((a) => a.kind === "original" && a.draftPath === draftPath);
30
55
  if (!action) {
@@ -34,47 +59,47 @@ export function createIndependentPublishing(dependencies) {
34
59
  preparedAction.draftPath !== draftPath ||
35
60
  state.actions.some((a) => a.id === preparedAction.id))
36
61
  throw new Error("unique-prepared-original-required");
37
- const error = guardInput(state, preparedAction, independentExecution);
62
+ const error = this.#guardInput(state, preparedAction, independentExecution);
38
63
  if (error)
39
64
  throw new Error(error);
40
65
  action = structuredClone(preparedAction);
41
66
  state.actions.push(action);
42
- await save(snapshot, state);
67
+ await this.#save(snapshot, state);
43
68
  }
44
69
  if (action.status !== "prepared")
45
70
  throw new Error("existing-submission-do-not-resubmit");
46
- if (action.claim && store.ownerAlive(action.claim.pid))
71
+ if (action.claim && this.#store.ownerAlive(action.claim.pid))
47
72
  throw new Error("same-action-claimed");
48
- const error = guardInput(state, action, independentExecution);
73
+ const error = this.#guardInput(state, action, independentExecution);
49
74
  if (error)
50
75
  throw new Error(error);
51
76
  });
52
- const own = await platform.readAccount();
77
+ const own = await this.#platform.readAccount();
53
78
  if (own.needsStateCheck ||
54
79
  own.alerts?.length ||
55
80
  !same(own.accountHandle, action.accountHandle))
56
81
  throw new Error("fresh-account-unconfirmed");
57
- const baseline = await platform.findSubmission({
82
+ const baseline = await this.#platform.findSubmission({
58
83
  ...action,
59
- intentAt: stamp(),
84
+ intentAt: this.#stamp(),
60
85
  });
61
86
  if (baseline.needsStateCheck || baseline.alerts?.length)
62
87
  throw new Error("publication-baseline-unconfirmed");
63
- token = `${action.id}:${stamp()}:${Math.random()}`;
64
- await store.withLock(async () => {
65
- const snapshot = await load(taskId), state = structuredClone(snapshot.state);
88
+ token = `${action.id}:${this.#stamp()}:${Math.random()}`;
89
+ await this.#store.withLock(async () => {
90
+ const snapshot = await this.#load(taskId), state = structuredClone(snapshot.state);
66
91
  const target = state.actions.find((a) => a.id === action.id), item = state.items.find((i) => i.kind === "original");
67
92
  if (target?.status !== "prepared" ||
68
- (target.claim && store.ownerAlive(target.claim.pid)))
93
+ (target.claim && this.#store.ownerAlive(target.claim.pid)))
69
94
  throw new Error("action-already-claimed-do-not-resubmit");
70
- const error = guardInput(state, target, independentExecution);
95
+ const error = this.#guardInput(state, target, independentExecution);
71
96
  if (error)
72
97
  throw new Error(error);
73
98
  if (!inWindow() || item.verified + item.unknown >= item.planned)
74
99
  throw new Error("window-or-original-capacity-unavailable");
75
100
  Object.assign(target, {
76
101
  status: "intent",
77
- intentAt: stamp(),
102
+ intentAt: this.#stamp(),
78
103
  beforeIds: (baseline.posts ?? [])
79
104
  .map((p) => p.id)
80
105
  .filter(Boolean),
@@ -83,9 +108,9 @@ export function createIndependentPublishing(dependencies) {
83
108
  claim: {
84
109
  actionId: target.id,
85
110
  token,
86
- pid: store.processId,
111
+ pid: this.#store.processId,
87
112
  tabId: independentExecution.tabId,
88
- acquiredAt: stamp(),
113
+ acquiredAt: this.#stamp(),
89
114
  },
90
115
  });
91
116
  item.unknown += 1;
@@ -95,26 +120,26 @@ export function createIndependentPublishing(dependencies) {
95
120
  kind: "original",
96
121
  at: target.intentAt,
97
122
  });
98
- await save(snapshot, state);
123
+ await this.#save(snapshot, state);
99
124
  action = structuredClone(target);
100
125
  });
101
126
  let observation;
102
127
  try {
103
- await platform.submitPost({
128
+ await this.#platform.submitPost({
104
129
  ...action,
105
130
  beforeSubmit: async () => {
106
- const latest = await load(taskId), target = latest.state.actions.find((a) => a.id === action.id);
131
+ const latest = await this.#load(taskId), target = latest.state.actions.find((a) => a.id === action.id);
107
132
  if (target?.status !== "intent" ||
108
133
  target.claimToken !== token ||
109
134
  !inWindow())
110
135
  throw new Error("action-claim-or-window-changed");
111
- const error = guardInput(latest.state, target, independentExecution);
136
+ const error = this.#guardInput(latest.state, target, independentExecution);
112
137
  if (error)
113
138
  throw new Error(error);
114
139
  submissionStarted = true;
115
140
  },
116
141
  });
117
- observation = await platform.findSubmission(action);
142
+ observation = await this.#platform.findSubmission(action);
118
143
  }
119
144
  catch (error) {
120
145
  if (!submissionStarted)
@@ -125,11 +150,11 @@ export function createIndependentPublishing(dependencies) {
125
150
  error: error.message.slice(0, 240),
126
151
  };
127
152
  }
128
- return await store.withLock(async () => {
129
- const snapshot = await load(taskId), target = snapshot.state.actions.find((a) => a.id === action.id);
153
+ return await this.#store.withLock(async () => {
154
+ const snapshot = await this.#load(taskId), target = snapshot.state.actions.find((a) => a.id === action.id);
130
155
  if (target?.claimToken !== token || target.status !== "intent")
131
156
  throw new Error("action-settlement-conflict");
132
- await settleProof(snapshot, target, observation, {
157
+ await this.#settleProof(snapshot, target, observation, {
133
158
  preserveStatus: true,
134
159
  });
135
160
  return {
@@ -137,25 +162,25 @@ export function createIndependentPublishing(dependencies) {
137
162
  taskId,
138
163
  actionId: action.id,
139
164
  publicId: observation.publicId,
140
- evidenceRef: store.path,
165
+ evidenceRef: this.#store.path,
141
166
  mayEnd: false,
142
167
  };
143
168
  });
144
169
  }
145
170
  catch (error) {
146
171
  if (token && !submissionStarted)
147
- await store.withLock(async () => {
148
- const snapshot = await load(taskId), state = structuredClone(snapshot.state), target = state.actions.find((a) => a.id === action.id);
172
+ await this.#store.withLock(async () => {
173
+ const snapshot = await this.#load(taskId), state = structuredClone(snapshot.state), target = state.actions.find((a) => a.id === action.id);
149
174
  if (target?.claimToken !== token || target.status !== "intent")
150
175
  return;
151
176
  target.status = "prepared";
152
- target.preparationEvidence = await store.recordProof({
153
- observedAt: stamp(),
177
+ target.preparationEvidence = await this.#store.recordProof({
178
+ observedAt: this.#stamp(),
154
179
  submissionStarted: false,
155
180
  error: error.message.slice(0, 240),
156
181
  });
157
182
  state.items.find((i) => i.kind === "original").unknown -= 1;
158
- await save(snapshot, state);
183
+ await this.#save(snapshot, state);
159
184
  });
160
185
  return {
161
186
  status: "needsDecision",
@@ -172,7 +197,7 @@ export function createIndependentPublishing(dependencies) {
172
197
  }
173
198
  finally {
174
199
  if (token)
175
- await store.transaction((snapshot) => {
200
+ await this.#store.transaction((snapshot) => {
176
201
  if (snapshot.state.batchId !== taskId)
177
202
  return null;
178
203
  const next = structuredClone(snapshot.state), target = next.actions.find((a) => a.id === action.id);
@@ -183,11 +208,10 @@ export function createIndependentPublishing(dependencies) {
183
208
  });
184
209
  }
185
210
  }
186
- // Stable prepared actions are adopted in the original ledger, never in a second queue.
187
- async function prepareActionsForCall(taskId, actions = []) {
211
+ async handlePrepareActionsForCall(taskId, actions = []) {
188
212
  if (!actions.length)
189
213
  return;
190
- await store.transaction(async (snapshot) => {
214
+ await this.#store.transaction(async (snapshot) => {
191
215
  if (snapshot.state.batchId !== taskId)
192
216
  throw new Error("original-task-id-required");
193
217
  const next = structuredClone(snapshot.state);
@@ -200,7 +224,7 @@ export function createIndependentPublishing(dependencies) {
200
224
  !kinds.includes(action.kind))
201
225
  throw new Error("unique-prepared-action-required");
202
226
  ids.add(action.id);
203
- const inputError = guardInput(next, action);
227
+ const inputError = this.#guardInput(next, action);
204
228
  if (inputError)
205
229
  throw new Error(inputError);
206
230
  const existing = next.actions.find((a) => a.id === action.id);
@@ -232,9 +256,11 @@ export function createIndependentPublishing(dependencies) {
232
256
  throw new Error("prepared-actions-exceed-original-target");
233
257
  next.actions.push(structuredClone(action));
234
258
  }
235
- next.updatedAt = stamp();
259
+ next.updatedAt = this.#stamp();
236
260
  return next;
237
261
  });
238
262
  }
239
- return { publishIndependent, prepareActionsForCall };
263
+ }
264
+ export function createIndependentPublishing(...args) {
265
+ return new IndependentPublishing(...args);
240
266
  }
@@ -1,4 +1,8 @@
1
1
  import type { GrowthDependencies } from "./growth-dependencies.js";
2
- export declare function createGrowthReading(dependencies: Pick<GrowthDependencies, "platform" | "executor" | "store" | "runtimeId" | "stamp" | "releaseSession" | "session">): {
2
+ export declare class GrowthReading {
3
+ #private;
3
4
  readTask: (operation: any, { mode, ...input }?: any) => Promise<any>;
4
- };
5
+ constructor(dependencies: Pick<GrowthDependencies, "platform" | "executor" | "store" | "runtimeId" | "stamp" | "releaseSession" | "session">);
6
+ private handleReadTask;
7
+ }
8
+ export declare function createGrowthReading(...args: ConstructorParameters<typeof GrowthReading>): GrowthReading;
@@ -1,57 +1,77 @@
1
- export function createGrowthReading(dependencies) {
2
- const { platform, executor, store, runtimeId, stamp, releaseSession, session, } = dependencies;
3
- const readCache = new Map();
4
- async function readTask(operation, { mode = "receipt", ...input } = {}) {
1
+ export class GrowthReading {
2
+ #platform;
3
+ #executor;
4
+ #store;
5
+ #runtimeId;
6
+ #stamp;
7
+ #releaseSession;
8
+ #session;
9
+ #readCache;
10
+ readTask;
11
+ constructor(dependencies) {
12
+ const { platform, executor, store, runtimeId, stamp, releaseSession, session, } = dependencies;
13
+ this.#platform = platform;
14
+ this.#executor = executor;
15
+ this.#store = store;
16
+ this.#runtimeId = runtimeId;
17
+ this.#stamp = stamp;
18
+ this.#releaseSession = releaseSession;
19
+ this.#session = session;
20
+ this.#readCache = new Map();
21
+ this.readTask = this.handleReadTask.bind(this);
22
+ }
23
+ async handleReadTask(operation, { mode = "receipt", ...input } = {}) {
24
+ const executor = this.#executor;
5
25
  if (!["decision", "receipt", "delta"].includes(mode))
6
26
  throw new Error("读取模式须为decision/receipt/delta");
7
- if (session.running)
27
+ if (this.#session.running)
8
28
  return {
9
29
  status: "needsDecision",
10
30
  complete: false,
11
31
  reason: "same-tab-operation-running",
12
32
  };
13
- const caps = platform.preflight({
33
+ const caps = this.#platform.preflight({
14
34
  operation: ["readAccount", "readPerson", "readPost"].includes(operation)
15
35
  ? "read"
16
36
  : operation,
17
37
  input,
18
38
  });
19
- if (!caps.covered || typeof platform[operation] !== "function")
39
+ if (!caps.covered || typeof this.#platform[operation] !== "function")
20
40
  return {
21
41
  status: "needsCapability",
22
42
  ...caps,
23
43
  missing: [
24
44
  ...(caps.missing ?? []),
25
- ...(typeof platform[operation] === "function"
45
+ ...(typeof this.#platform[operation] === "function"
26
46
  ? []
27
47
  : [`platform.${operation}`]),
28
48
  ],
29
49
  };
30
- session.running = true;
50
+ this.#session.running = true;
31
51
  try {
32
52
  if (executor)
33
- await store.transaction((latest) => {
53
+ await this.#store.transaction((latest) => {
34
54
  const next = structuredClone(latest.state);
35
55
  next.executions ??= {};
36
56
  if (Object.values(next.executions).some((e) => (e.tabId === executor.tabId || e.id === executor.id) &&
37
- e.runtimeId !== runtimeId &&
57
+ e.runtimeId !== this.#runtimeId &&
38
58
  (e.activity || e.actionId) &&
39
- store.ownerAlive(e.pid)))
59
+ this.#store.ownerAlive(e.pid)))
40
60
  throw new Error("same-tab-or-executor-claimed");
41
61
  next.executions[executor.id] = {
42
62
  ...next.executions[executor.id],
43
63
  ...executor,
44
- runtimeId,
45
- pid: store.processId,
64
+ runtimeId: this.#runtimeId,
65
+ pid: this.#store.processId,
46
66
  activity: operation,
47
67
  status: next.executions[executor.id]?.status ?? "RUNNING",
48
- updatedAt: stamp(),
68
+ updatedAt: this.#stamp(),
49
69
  };
50
- next.updatedAt = stamp();
70
+ next.updatedAt = this.#stamp();
51
71
  return next;
52
72
  });
53
- const value = await platform[operation](input);
54
- const evidenceRef = await store.recordProof(await platform.captureProof({ observation: value }));
73
+ const value = await this.#platform[operation](input);
74
+ const evidenceRef = await this.#store.recordProof(await this.#platform.captureProof({ observation: value }));
55
75
  const status = value.status ??
56
76
  (value.complete === false ||
57
77
  value.needsStateCheck ||
@@ -86,11 +106,11 @@ export function createGrowthReading(dependencies) {
86
106
  for (const entry of value[collection]) {
87
107
  const key = JSON.stringify([operation, input, entry.id]);
88
108
  const normalized = JSON.stringify(entry, (name, val) => name === "observedAt" ? undefined : val);
89
- if (readCache.get(key) === normalized)
109
+ if (this.#readCache.get(key) === normalized)
90
110
  unchangedIds.push(entry.id);
91
111
  else
92
112
  changed.push(entry);
93
- readCache.set(key, normalized);
113
+ this.#readCache.set(key, normalized);
94
114
  }
95
115
  return { ...receipt, [collection]: changed, unchangedIds };
96
116
  }
@@ -108,8 +128,8 @@ export function createGrowthReading(dependencies) {
108
128
  ? "connection-failed"
109
129
  : "observation-unconfirmed";
110
130
  const diagnostic = { operation, category, code };
111
- const evidenceRef = await store.recordProof({
112
- capturedAt: stamp(),
131
+ const evidenceRef = await this.#store.recordProof({
132
+ capturedAt: this.#stamp(),
113
133
  diagnostic: { ...diagnostic, error: error.message },
114
134
  });
115
135
  return {
@@ -124,12 +144,14 @@ export function createGrowthReading(dependencies) {
124
144
  }
125
145
  finally {
126
146
  try {
127
- await releaseSession();
147
+ await this.#releaseSession();
128
148
  }
129
149
  finally {
130
- session.running = false;
150
+ this.#session.running = false;
131
151
  }
132
152
  }
133
153
  }
134
- return { readTask };
154
+ }
155
+ export function createGrowthReading(...args) {
156
+ return new GrowthReading(...args);
135
157
  }
@@ -1,6 +1,9 @@
1
1
  import type { GrowthDependencies } from "./growth-dependencies.js";
2
- export declare function createGrowthRecovery(dependencies: Pick<GrowthDependencies, "capability" | "platform" | "load" | "evidenceExists" | "clock" | "store" | "settleProof" | "executor" | "parallelRound" | "round" | "readTask">): {
2
+ export declare class GrowthRecovery {
3
+ #private;
4
+ constructor(dependencies: Pick<GrowthDependencies, "capability" | "platform" | "load" | "evidenceExists" | "clock" | "store" | "settleProof" | "executor" | "parallelRound" | "round" | "readTask">);
3
5
  verifyPublishedPost({ taskId, draftPath, publicURL, evidenceRef, sourceLink, }: any): Promise<any>;
4
- verifySubmission: ({ taskId, actionId }: any) => Promise<any>;
5
- verifyFollow: (target: any) => Promise<any>;
6
- };
6
+ verifySubmission({ taskId, actionId }: any): Promise<any>;
7
+ verifyFollow(target: any): Promise<any>;
8
+ }
9
+ export declare function createGrowthRecovery(...args: ConstructorParameters<typeof GrowthRecovery>): GrowthRecovery;