@nextclaw/x-harness 0.1.0 → 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 (113) hide show
  1. package/LICENSE +231 -6
  2. package/README.md +17 -3
  3. package/SOURCE.md +9 -0
  4. package/cli/main.js +232 -188
  5. package/dist/api.d.ts +56 -2
  6. package/dist/api.js +178 -75
  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 +9 -0
  11. package/dist/browser-dom-capabilities.js +131 -0
  12. package/dist/browser-dom-dependencies.d.ts +16 -0
  13. package/dist/browser-dom-dependencies.js +1 -0
  14. package/dist/browser-dom-editing.d.ts +166 -0
  15. package/dist/browser-dom-editing.js +418 -0
  16. package/dist/browser-dom-navigation.d.ts +18 -0
  17. package/dist/browser-dom-navigation.js +181 -0
  18. package/dist/browser-dom-network.d.ts +1 -0
  19. package/dist/browser-dom-network.js +128 -0
  20. package/dist/browser-dom-submission.d.ts +7 -0
  21. package/dist/browser-dom-submission.js +101 -0
  22. package/dist/browser-dom.d.ts +14 -2
  23. package/dist/browser-dom.js +120 -824
  24. package/dist/browser-native.d.ts +14 -1
  25. package/dist/browser-native.js +180 -143
  26. package/dist/browser.js +6 -2
  27. package/dist/creator-native.d.ts +3 -0
  28. package/dist/creator-native.js +113 -0
  29. package/dist/creator.js +1 -1
  30. package/dist/diagnostics.d.ts +11 -0
  31. package/dist/diagnostics.js +10 -0
  32. package/dist/growth-batch.d.ts +8 -0
  33. package/dist/growth-batch.js +190 -0
  34. package/dist/growth-constants.d.ts +5 -0
  35. package/dist/growth-constants.js +13 -0
  36. package/dist/growth-dependencies.d.ts +33 -0
  37. package/dist/growth-dependencies.js +1 -0
  38. package/dist/growth-execution.d.ts +17 -0
  39. package/dist/growth-execution.js +346 -0
  40. package/dist/growth-parallel.d.ts +8 -0
  41. package/dist/growth-parallel.js +332 -0
  42. package/dist/growth-plan.d.ts +9 -0
  43. package/dist/growth-plan.js +229 -0
  44. package/dist/growth-policy.d.ts +18 -0
  45. package/dist/growth-policy.js +208 -0
  46. package/dist/growth-publishing.d.ts +10 -0
  47. package/dist/growth-publishing.js +266 -0
  48. package/dist/growth-reading.d.ts +8 -0
  49. package/dist/growth-reading.js +157 -0
  50. package/dist/growth-recovery.d.ts +9 -0
  51. package/dist/growth-recovery.js +187 -0
  52. package/dist/growth-state.d.ts +16 -0
  53. package/dist/growth-state.js +220 -0
  54. package/dist/growth-tasks.d.ts +20 -0
  55. package/dist/growth-tasks.js +164 -0
  56. package/dist/growth.d.ts +26 -1
  57. package/dist/growth.js +211 -1908
  58. package/dist/platform-capabilities.d.ts +8 -0
  59. package/dist/platform-capabilities.js +29 -0
  60. package/dist/platform-collections.d.ts +14 -0
  61. package/dist/platform-collections.js +250 -0
  62. package/dist/platform-dependencies.d.ts +43 -0
  63. package/dist/platform-dependencies.js +1 -0
  64. package/dist/platform-diagnostics.d.ts +14 -0
  65. package/dist/platform-diagnostics.js +63 -0
  66. package/dist/platform-observation.d.ts +16 -0
  67. package/dist/platform-observation.js +96 -0
  68. package/dist/platform-rules.d.ts +17 -0
  69. package/dist/platform-rules.js +41 -0
  70. package/dist/platform-targets.d.ts +14 -0
  71. package/dist/platform-targets.js +154 -0
  72. package/dist/platform-writing.d.ts +16 -0
  73. package/dist/platform-writing.js +202 -0
  74. package/dist/platform.d.ts +23 -2
  75. package/dist/platform.js +125 -678
  76. package/dist/readers-actions.d.ts +2 -0
  77. package/dist/readers-actions.js +66 -0
  78. package/dist/readers-ax-host.d.ts +12 -0
  79. package/dist/readers-ax-host.js +134 -0
  80. package/dist/readers-ax-parser.d.ts +1 -0
  81. package/dist/readers-ax-parser.js +69 -0
  82. package/dist/readers-ax-program.d.ts +1 -0
  83. package/dist/readers-ax-program.js +5 -0
  84. package/dist/readers-ax-tree.d.ts +19 -0
  85. package/dist/readers-ax-tree.js +111 -0
  86. package/dist/readers-ax-views.d.ts +87 -0
  87. package/dist/readers-ax-views.js +247 -0
  88. package/dist/readers-cli.d.ts +6 -0
  89. package/dist/readers-cli.js +79 -0
  90. package/dist/readers-composer.d.ts +2 -0
  91. package/dist/readers-composer.js +131 -0
  92. package/dist/readers-navigation.d.ts +2 -0
  93. package/dist/readers-navigation.js +66 -0
  94. package/dist/readers-posts.d.ts +2 -0
  95. package/dist/readers-posts.js +141 -0
  96. package/dist/readers-profile.d.ts +3 -0
  97. package/dist/readers-profile.js +157 -0
  98. package/dist/readers.d.ts +9 -20
  99. package/dist/readers.js +9 -1045
  100. package/dist/results.js +1 -1
  101. package/dist/round-state.js +20 -3
  102. package/dist/store.d.ts +22 -4
  103. package/dist/store.js +173 -151
  104. package/dist/types.d.ts +10 -0
  105. package/dist/types.js +1 -0
  106. package/generated/observation.mjs +3 -2
  107. package/package.json +23 -9
  108. package/skill/SKILL.md +17 -36
  109. package/skill/references/api.md +7 -34
  110. package/skill/references/reads.md +23 -0
  111. package/skill/references/recovery.md +25 -0
  112. package/skill/references/setup.md +23 -0
  113. package/skill/references/writes.md +51 -0
@@ -0,0 +1,187 @@
1
+ import { xURL } from "./platform-rules.js";
2
+ import { same } from "./growth-constants.js";
3
+ export class GrowthRecovery {
4
+ #capability;
5
+ #platform;
6
+ #load;
7
+ #evidenceExists;
8
+ #clock;
9
+ #store;
10
+ #settleProof;
11
+ #executor;
12
+ #parallelRound;
13
+ #round;
14
+ #readTask;
15
+ constructor(dependencies) {
16
+ this.verifyFollow = this.verifyFollow.bind(this);
17
+ this.verifySubmission = this.verifySubmission.bind(this);
18
+ this.verifyPublishedPost = this.verifyPublishedPost.bind(this);
19
+ const { capability, platform, load, evidenceExists, clock, store, settleProof, executor, parallelRound, round, readTask, } = dependencies;
20
+ this.#capability = capability;
21
+ this.#platform = platform;
22
+ this.#load = load;
23
+ this.#evidenceExists = evidenceExists;
24
+ this.#clock = clock;
25
+ this.#store = store;
26
+ this.#settleProof = settleProof;
27
+ this.#executor = executor;
28
+ this.#parallelRound = parallelRound;
29
+ this.#round = round;
30
+ this.#readTask = readTask;
31
+ }
32
+ async verifyPublishedPost({ taskId, draftPath, publicURL, evidenceRef, sourceLink, }) {
33
+ // Read-only recovery of a single submitted original; never opens a composer.
34
+ const caps = this.#capability("read");
35
+ if (!caps.covered || typeof this.#platform.readPost !== "function")
36
+ return {
37
+ status: "needsCapability",
38
+ missing: ["platform.readPost", ...caps.missing],
39
+ };
40
+ const initial = await this.#load(taskId);
41
+ const action = initial.state.actions.find((a) => a.kind === "original" && a.draftPath === draftPath);
42
+ if (!action ||
43
+ !["intent", "unknown"].includes(action.status) ||
44
+ !this.#evidenceExists(evidenceRef))
45
+ throw new Error("submitted-original-and-public-proof-required");
46
+ const url = xURL(publicURL), postId = url.pathname.match(/\/status\/(\d+)$/)?.[1];
47
+ if (!postId ||
48
+ url.pathname.toLowerCase() !==
49
+ `/${action.accountHandle}/status/${postId}`.toLowerCase())
50
+ throw new Error("public-account-mismatch");
51
+ const observation = await this.#platform.readPost({
52
+ postId,
53
+ url: publicURL,
54
+ evidenceRef,
55
+ });
56
+ const trailing = action.text.match(/\n\n(https:\/\/\S+)$/);
57
+ const matches = (observation.posts ?? []).filter((p) => {
58
+ const exact = p.body === action.text;
59
+ const card = trailing &&
60
+ sourceLink &&
61
+ this.#evidenceExists(sourceLink.evidenceRef) &&
62
+ sourceLink.resolvedURL === trailing[1] &&
63
+ p.links?.includes(sourceLink.href) &&
64
+ p.body === action.text.slice(0, -trailing[0].length);
65
+ let renderedSource = false;
66
+ if (trailing &&
67
+ sourceLink?.renderedText &&
68
+ this.#evidenceExists(sourceLink.evidenceRef) &&
69
+ sourceLink.resolvedURL === trailing[1] &&
70
+ p.links?.includes(sourceLink.href)) {
71
+ try {
72
+ renderedSource =
73
+ new URL(sourceLink.renderedText.replace(/\s/g, "")).href ===
74
+ sourceLink.resolvedURL &&
75
+ p.body?.endsWith(sourceLink.renderedText) &&
76
+ p.body.slice(0, -sourceLink.renderedText.length).trimEnd() ===
77
+ action.text.slice(0, -trailing[0].length);
78
+ }
79
+ catch {
80
+ /* Invalid rendered URL cannot verify publication. */
81
+ }
82
+ }
83
+ return (p.id === postId &&
84
+ same(p.author, action.accountHandle) &&
85
+ (exact || card || renderedSource) &&
86
+ !p.needsExpansion &&
87
+ !(action.beforeIds ?? []).includes(p.id) &&
88
+ Date.parse(p.datetime) >= Date.parse(action.intentAt) &&
89
+ Date.parse(p.datetime) <= this.#clock() + 5000 &&
90
+ (!action.assets?.length ||
91
+ action.assets.every((a) => p.images?.some((i) => i.alt === a.alt))));
92
+ });
93
+ Object.assign(observation, {
94
+ verified: !observation.needsStateCheck &&
95
+ !observation.alerts?.length &&
96
+ matches.length === 1,
97
+ publicId: matches.length === 1 ? postId : null,
98
+ sourceLink,
99
+ });
100
+ return this.#store.withLock(async () => {
101
+ const latest = await this.#load(taskId), target = latest.state.actions.find((a) => a.id === action.id);
102
+ if (!["intent", "unknown"].includes(target?.status) ||
103
+ target.intentAt !== action.intentAt ||
104
+ target.text !== action.text)
105
+ throw new Error("recovery-action-changed");
106
+ await this.#settleProof(latest, target, observation, {
107
+ preserveStatus: true,
108
+ });
109
+ return {
110
+ status: observation.verified ? "verified" : "unknown",
111
+ publicId: observation.publicId,
112
+ taskId,
113
+ actionId: action.id,
114
+ mayEnd: false,
115
+ };
116
+ });
117
+ }
118
+ async verifySubmission({ taskId, actionId }) {
119
+ const executor = this.#executor;
120
+ const snapshot = await this.#load(taskId), action = snapshot.state.actions?.find((a) => a.id === actionId);
121
+ if (!action || !["intent", "unknown"].includes(action.status))
122
+ return {
123
+ status: "needsDecision",
124
+ reason: "verification-requires-original-unknown-or-intent",
125
+ mayEnd: false,
126
+ };
127
+ return (executor ? this.#parallelRound : this.#round)({
128
+ taskId,
129
+ requestedKind: action.kind,
130
+ selector: (a) => a.id === actionId,
131
+ });
132
+ }
133
+ async verifyFollow(target) {
134
+ const mode = target.mode ?? "receipt";
135
+ if (!["receipt", "decision"].includes(mode))
136
+ throw new Error("关注核验模式须为receipt/decision");
137
+ const caps = this.#platform.preflight({ operation: "read" });
138
+ if (!caps.covered)
139
+ return {
140
+ handle: target.handle,
141
+ verified: false,
142
+ status: "needsCapability",
143
+ ...caps,
144
+ };
145
+ if (!target.url || !target.evidenceRef) {
146
+ const original = (await this.#store.load()).state.actions?.find((a) => a.kind === "follow" && same(a.handle, target.handle));
147
+ if (!original)
148
+ return {
149
+ handle: target.handle,
150
+ verified: false,
151
+ status: "needsDecision",
152
+ reason: "original-person-observation-required",
153
+ };
154
+ target = original;
155
+ }
156
+ const value = await this.#readTask("readPerson", {
157
+ ...target,
158
+ mode: "decision",
159
+ });
160
+ return {
161
+ handle: target.handle,
162
+ status: value.status,
163
+ fresh: value.fresh,
164
+ verified: value.status === "observed" &&
165
+ !value.needsStateCheck &&
166
+ !value.alerts?.length &&
167
+ value.relationship === "following" &&
168
+ (!target.accountHandle ||
169
+ same(value.accountHandle, target.accountHandle)),
170
+ ...(value.evidenceRef ? { evidenceRef: value.evidenceRef } : {}),
171
+ ...(value.missing?.length ? { missing: value.missing } : {}),
172
+ ...(value.diagnostic
173
+ ? {
174
+ diagnostic: {
175
+ operation: value.diagnostic.operation,
176
+ category: value.diagnostic.category,
177
+ code: value.diagnostic.code,
178
+ },
179
+ }
180
+ : {}),
181
+ ...(mode === "decision" ? { observation: value } : {}),
182
+ };
183
+ }
184
+ }
185
+ export function createGrowthRecovery(...args) {
186
+ return new GrowthRecovery(...args);
187
+ }
@@ -0,0 +1,16 @@
1
+ import type { GrowthDependencies } from "./growth-dependencies.js";
2
+ export declare class GrowthState {
3
+ #private;
4
+ load: (taskId: any) => Promise<any>;
5
+ save: (snapshot: any, state: any) => Promise<any>;
6
+ result: (snapshot: any, needs?: any) => any;
7
+ settleProof: (snapshot: any, action: any, observation: any, { preserveStatus }?: any) => Promise<any>;
8
+ releaseSession: (taskId?: any) => Promise<any>;
9
+ constructor(dependencies: Pick<GrowthDependencies, "store" | "project" | "clock" | "evidenceExists" | "stamp" | "executor" | "waitBudget" | "platform" | "runtimeId" | "session">);
10
+ private handleLoad;
11
+ private handleSave;
12
+ private handleResult;
13
+ private handleSettleProof;
14
+ private handleReleaseSession;
15
+ }
16
+ export declare function createGrowthState(...args: ConstructorParameters<typeof GrowthState>): GrowthState;
@@ -0,0 +1,220 @@
1
+ import { checkRound } from "./round-state.js";
2
+ import { kinds } from "./growth-constants.js";
3
+ export class GrowthState {
4
+ #store;
5
+ #project;
6
+ #clock;
7
+ #evidenceExists;
8
+ #stamp;
9
+ #executor;
10
+ #waitBudget;
11
+ #platform;
12
+ #runtimeId;
13
+ #session;
14
+ load;
15
+ save;
16
+ result;
17
+ settleProof;
18
+ releaseSession;
19
+ constructor(dependencies) {
20
+ const { store, project, clock, evidenceExists, stamp, executor, waitBudget, platform, runtimeId, session, } = dependencies;
21
+ this.#store = store;
22
+ this.#project = project;
23
+ this.#clock = clock;
24
+ this.#evidenceExists = evidenceExists;
25
+ this.#stamp = stamp;
26
+ this.#executor = executor;
27
+ this.#waitBudget = waitBudget;
28
+ this.#platform = platform;
29
+ this.#runtimeId = runtimeId;
30
+ this.#session = session;
31
+ this.load = this.handleLoad.bind(this);
32
+ this.save = this.handleSave.bind(this);
33
+ this.result = this.handleResult.bind(this);
34
+ this.settleProof = this.handleSettleProof.bind(this);
35
+ this.releaseSession = this.handleReleaseSession.bind(this);
36
+ }
37
+ async handleLoad(taskId) {
38
+ const snapshot = await this.#store.load();
39
+ if (snapshot.state.batchId !== taskId)
40
+ throw new Error("任务ID不是原台账当前批次");
41
+ // Expired WAITING is actionable, not a malformed ledger. Validate structure separately from stopping.
42
+ const gate = checkRound({ ...this.#project(snapshot.state), status: "RUNNING" }, { now: this.#clock(), evidenceExists: this.#evidenceExists });
43
+ if (!gate.valid)
44
+ throw new Error(`原轮次标记无效:${gate.errors.join(";")}`);
45
+ const actions = snapshot.state.actions ?? [];
46
+ if (new Set(actions.map((a) => a.id)).size !== actions.length)
47
+ throw new Error("原队列动作ID重复");
48
+ for (const a of actions) {
49
+ if (!a.id ||
50
+ !kinds.includes(a.kind) ||
51
+ ![
52
+ "prepared",
53
+ "intent",
54
+ "unknown",
55
+ "verified",
56
+ "preexisting",
57
+ "skipped",
58
+ ].includes(a.status))
59
+ throw new Error("原队列动作结构无效");
60
+ }
61
+ return snapshot;
62
+ }
63
+ async handleSave(snapshot, state) {
64
+ const executor = this.#executor;
65
+ state.updatedAt = this.#stamp();
66
+ if (!executor)
67
+ return this.#store.save(snapshot, state);
68
+ // Batch metadata still uses a short withLock transaction; it never contains browser I/O.
69
+ if (!this.#session.claim)
70
+ return this.#store.save(snapshot, state);
71
+ return this.#store.transaction((latest) => {
72
+ const next = structuredClone(latest.state), before = snapshot.state.actions.find((a) => a.id === this.#session.claim.actionId);
73
+ const entry = next.actions.find((a) => a.id === this.#session.claim.actionId), after = state.actions.find((a) => a.id === this.#session.claim.actionId);
74
+ if (next.batchId !== snapshot.state.batchId ||
75
+ entry?.claim?.token !== this.#session.claim.token ||
76
+ JSON.stringify({ ...entry, claim: undefined }) !==
77
+ JSON.stringify({ ...before, claim: undefined }))
78
+ throw new Error("action-claim-or-version-conflict");
79
+ const oldItem = snapshot.state.items.find((i) => i.kind === entry.kind), newItem = state.items.find((i) => i.kind === entry.kind);
80
+ const item = next.items.find((i) => i.kind === entry.kind);
81
+ const reservation = before.status === "prepared" && after.status === "intent";
82
+ if (reservation) {
83
+ if (!item.actionable || item.verified + item.unknown >= item.planned)
84
+ throw new Error("shared-target-capacity-exhausted");
85
+ if (entry.kind === "follow" && this.#waitBudget(next))
86
+ throw new Error("shared-follow-budget-exhausted");
87
+ }
88
+ if (after.status === "verified" &&
89
+ entry.kind !== "follow" &&
90
+ next.actions.some((a) => a.id !== entry.id &&
91
+ a.status === "verified" &&
92
+ a.publicId === after.publicId))
93
+ throw new Error("同一公开结果不能重复结算");
94
+ Object.assign(entry, after, { claim: entry.claim });
95
+ item.verified += newItem.verified - oldItem.verified;
96
+ item.unknown += newItem.unknown - oldItem.unknown;
97
+ if (item.unknown < 0 || item.verified + item.unknown > item.planned)
98
+ throw new Error("并行结算越界");
99
+ item.resultEvidence = [
100
+ ...new Set([
101
+ ...(item.resultEvidence ?? []),
102
+ ...(newItem.resultEvidence ?? []),
103
+ ]),
104
+ ];
105
+ for (const key of [
106
+ "actionable",
107
+ "reason",
108
+ "resumeAt",
109
+ "resumeWhen",
110
+ "blockEvidence",
111
+ ]) {
112
+ if (JSON.stringify(oldItem[key]) !== JSON.stringify(newItem[key])) {
113
+ if (key in newItem)
114
+ item[key] = newItem[key];
115
+ else
116
+ delete item[key];
117
+ }
118
+ }
119
+ if (reservation) {
120
+ const attempt = state.attempts.at(-1);
121
+ next.attempts ??= [];
122
+ next.attempts.push(attempt);
123
+ if (entry.kind === "follow") {
124
+ next.execution ??= {};
125
+ const old = next.execution.followGroup;
126
+ next.execution.followGroup = {
127
+ attempts: old?.attempts < 10 &&
128
+ this.#clock() < Date.parse(old.lastAt) + 180000
129
+ ? old.attempts + 1
130
+ : 1,
131
+ lastAt: attempt.at,
132
+ };
133
+ }
134
+ }
135
+ next.status = "RUNNING";
136
+ next.updatedAt = this.#stamp();
137
+ return next;
138
+ });
139
+ }
140
+ handleResult(snapshot, needs = []) {
141
+ const gate = checkRound(this.#project(snapshot.state), {
142
+ now: this.#clock(),
143
+ evidenceExists: this.#evidenceExists,
144
+ });
145
+ return {
146
+ status: gate.mayEnd
147
+ ? gate.status === "COMPLETE"
148
+ ? "complete"
149
+ : "waiting"
150
+ : needs.some((n) => n.status === "needsCapability")
151
+ ? "needsCapability"
152
+ : "needsDecision",
153
+ taskId: snapshot.state.batchId,
154
+ mayEnd: gate.mayEnd,
155
+ scopeKinds: gate.scopeKinds,
156
+ actual: Object.fromEntries(snapshot.state.items.map((i) => [
157
+ i.kind,
158
+ { verified: i.verified, unknown: i.unknown },
159
+ ])),
160
+ gaps: gate.pending,
161
+ ...(needs.length ? { decision: needs } : {}),
162
+ evidenceRef: snapshot.path ?? this.#store.path,
163
+ ...(gate.errors.length ? { settlementErrors: gate.errors } : {}),
164
+ };
165
+ }
166
+ async handleSettleProof(snapshot, action, observation, { preserveStatus = false } = {}) {
167
+ const proof = await this.#store.recordProof(await this.#platform.captureProof({ observation }));
168
+ const state = structuredClone(snapshot.state);
169
+ const entry = state.actions.find((a) => a.id === action.id);
170
+ const item = state.items.find((i) => i.kind === action.kind);
171
+ if (observation.verified) {
172
+ if (action.kind !== "follow" &&
173
+ (!/^\d+$/.test(observation.publicId ?? "") ||
174
+ state.actions.some((a) => a.id !== action.id &&
175
+ a.status === "verified" &&
176
+ a.publicId === observation.publicId)))
177
+ throw new Error("同一公开结果不能重复结算");
178
+ if (["intent", "unknown"].includes(entry.status))
179
+ item.unknown -= 1;
180
+ if (entry.status !== "verified")
181
+ item.verified += 1;
182
+ entry.status = "verified";
183
+ entry.publicId = observation.publicId;
184
+ entry.resultEvidence = proof;
185
+ item.resultEvidence = [
186
+ ...new Set([...(item.resultEvidence ?? []), proof]),
187
+ ];
188
+ }
189
+ else {
190
+ if (!["intent", "unknown"].includes(entry.status))
191
+ item.unknown += 1;
192
+ entry.status = "unknown";
193
+ entry.resultEvidence = proof;
194
+ }
195
+ if (item.verified > item.planned || item.unknown < 0)
196
+ throw new Error("结算越界:不覆盖原目标或重复计数");
197
+ if (!preserveStatus)
198
+ state.status = "RUNNING";
199
+ return this.handleSave(snapshot, state);
200
+ }
201
+ async handleReleaseSession(taskId = undefined) {
202
+ const executor = this.#executor;
203
+ if (!executor)
204
+ return;
205
+ await this.#store.transaction((latest) => {
206
+ const own = latest.state.executions?.[executor.id];
207
+ if ((taskId && latest.state.batchId !== taskId) ||
208
+ own?.runtimeId !== this.#runtimeId ||
209
+ !own.activity)
210
+ return null;
211
+ const next = structuredClone(latest.state);
212
+ delete next.executions[executor.id].activity;
213
+ next.updatedAt = this.#stamp();
214
+ return next;
215
+ });
216
+ }
217
+ }
218
+ export function createGrowthState(...args) {
219
+ return new GrowthState(...args);
220
+ }
@@ -0,0 +1,20 @@
1
+ import type { GrowthDependencies } from "./growth-dependencies.js";
2
+ export declare class GrowthTasks {
3
+ #private;
4
+ constructor(dependencies: Pick<GrowthDependencies, "platform" | "capability" | "executor" | "readTask" | "parallelRound" | "round" | "load" | "result" | "publishIndependent" | "prepareActionsForCall">);
5
+ preflight({ operation, input }: any): any;
6
+ readCreator(args: any): Promise<any>;
7
+ inspectAccount(args: any): Promise<any>;
8
+ readPost(args: any): Promise<any>;
9
+ readPerson(args: any): Promise<any>;
10
+ readBookmarks(args: any): Promise<any>;
11
+ searchSources(args: any): Promise<any>;
12
+ readFeedback(args: any): Promise<any>;
13
+ runGrowthRound(args: any): Promise<any>;
14
+ resumeGrowthRound(args: any): Promise<any>;
15
+ inspectRound({ taskId }: any): Promise<any>;
16
+ followOne({ taskId, handle }: any): Promise<any>;
17
+ publishPost(input: any): Promise<any>;
18
+ replyToPost({ taskId, postId, text, preparedAction }: any): Promise<any>;
19
+ }
20
+ export declare function createGrowthTasks(...args: ConstructorParameters<typeof GrowthTasks>): GrowthTasks;
@@ -0,0 +1,164 @@
1
+ import { kinds, operations, same } from "./growth-constants.js";
2
+ export class GrowthTasks {
3
+ #platform;
4
+ #capability;
5
+ #executor;
6
+ #readTask;
7
+ #parallelRound;
8
+ #round;
9
+ #load;
10
+ #result;
11
+ #publishIndependent;
12
+ #prepareActionsForCall;
13
+ constructor(dependencies) {
14
+ this.replyToPost = this.replyToPost.bind(this);
15
+ this.publishPost = this.publishPost.bind(this);
16
+ this.followOne = this.followOne.bind(this);
17
+ this.inspectRound = this.inspectRound.bind(this);
18
+ this.resumeGrowthRound = this.resumeGrowthRound.bind(this);
19
+ this.runGrowthRound = this.runGrowthRound.bind(this);
20
+ this.readFeedback = this.readFeedback.bind(this);
21
+ this.searchSources = this.searchSources.bind(this);
22
+ this.readBookmarks = this.readBookmarks.bind(this);
23
+ this.readPerson = this.readPerson.bind(this);
24
+ this.readPost = this.readPost.bind(this);
25
+ this.inspectAccount = this.inspectAccount.bind(this);
26
+ this.readCreator = this.readCreator.bind(this);
27
+ this.preflight = this.preflight.bind(this);
28
+ const { platform, capability, executor, readTask, parallelRound, round, load, result, publishIndependent, prepareActionsForCall, } = dependencies;
29
+ this.#platform = platform;
30
+ this.#capability = capability;
31
+ this.#executor = executor;
32
+ this.#readTask = readTask;
33
+ this.#parallelRound = parallelRound;
34
+ this.#round = round;
35
+ this.#load = load;
36
+ this.#result = result;
37
+ this.#publishIndependent = publishIndependent;
38
+ this.#prepareActionsForCall = prepareActionsForCall;
39
+ }
40
+ preflight({ operation, input = {} }) {
41
+ const executor = this.#executor;
42
+ if (["readBookmarks", "searchSources", "readFeedback"].includes(operation))
43
+ return this.#platform.preflight({ operation, input });
44
+ if (["inspectRound", "verifySubmission"].includes(operation))
45
+ return this.#capability("read", input);
46
+ if (["inspectAccount", "verifyFollow"].includes(operation)) {
47
+ const name = operation === "verifyFollow" ? "readPerson" : "readAccount";
48
+ if (typeof this.#platform?.preflight !== "function" ||
49
+ typeof this.#platform?.[name] !== "function")
50
+ return {
51
+ covered: false,
52
+ missing: [name],
53
+ owner: "scripts/x-harness-platform.mjs",
54
+ };
55
+ return this.#platform.preflight({ operation: "read", input });
56
+ }
57
+ if (["runGrowthRound", "resumeGrowthRound"].includes(operation)) {
58
+ const scoped = executor?.scopeKinds ?? input.scopeKinds ?? kinds;
59
+ if (!Array.isArray(scoped) ||
60
+ !scoped.length ||
61
+ scoped.some((k) => !kinds.includes(k)))
62
+ return {
63
+ covered: false,
64
+ missing: ["scopeKinds"],
65
+ owner: "scripts/x-growth-harness.mjs",
66
+ };
67
+ const all = scoped.map((kind) => this.#capability(operations[kind], { ...input, kind }));
68
+ return {
69
+ covered: all.every((c) => c.covered),
70
+ missing: [...new Set(all.flatMap((c) => c.missing))],
71
+ owners: [
72
+ ...new Set(all.filter((c) => !c.covered).map((c) => c.owner)),
73
+ ],
74
+ };
75
+ }
76
+ if (![
77
+ "read",
78
+ "followOne",
79
+ "followBatch",
80
+ "publishPost",
81
+ "replyToPost",
82
+ ...Object.values(operations),
83
+ ].includes(operation))
84
+ return {
85
+ covered: false,
86
+ missing: [`operation.${operation}`],
87
+ owner: "scripts/x-growth-harness.mjs",
88
+ };
89
+ return this.#capability(operation, input);
90
+ }
91
+ readCreator(args) {
92
+ return this.#readTask("readCreator", args);
93
+ }
94
+ inspectAccount(args) {
95
+ return this.#readTask("readAccount", args);
96
+ }
97
+ readPost(args) {
98
+ return this.#readTask("readPost", { ...args, expand: true });
99
+ }
100
+ readPerson(args) {
101
+ return this.#readTask("readPerson", args);
102
+ }
103
+ readBookmarks(args) {
104
+ return this.#readTask("readBookmarks", args);
105
+ }
106
+ searchSources(args) {
107
+ return this.#readTask("searchSources", args);
108
+ }
109
+ readFeedback(args) {
110
+ return this.#readTask("readFeedback", args);
111
+ }
112
+ runGrowthRound(args) {
113
+ const executor = this.#executor;
114
+ return executor ? this.#parallelRound(args) : this.#round(args);
115
+ }
116
+ resumeGrowthRound(args) {
117
+ const executor = this.#executor;
118
+ return executor ? this.#parallelRound(args) : this.#round(args);
119
+ }
120
+ async inspectRound({ taskId }) {
121
+ const snapshot = await this.#load(taskId);
122
+ return this.#result(snapshot);
123
+ }
124
+ followOne({ taskId, handle }) {
125
+ const executor = this.#executor;
126
+ return (executor ? this.#parallelRound : this.#round)({
127
+ taskId,
128
+ requestedKind: "follow",
129
+ selector: (a) => a.kind === "follow" && same(a.handle, handle),
130
+ });
131
+ }
132
+ async publishPost(input) {
133
+ const executor = this.#executor;
134
+ if (!executor && input.independentExecution)
135
+ return this.#publishIndependent(input);
136
+ if (input.preparedAction &&
137
+ (input.preparedAction.kind !== "original" ||
138
+ input.preparedAction.draftPath !== input.draftPath))
139
+ throw new Error("prepared-original-target-mismatch");
140
+ await this.#prepareActionsForCall(input.taskId, input.preparedAction ? [input.preparedAction] : []);
141
+ return (executor ? this.#parallelRound : this.#round)({
142
+ taskId: input.taskId,
143
+ requestedKind: "original",
144
+ selector: (a) => a.kind === "original" && a.draftPath === input.draftPath,
145
+ });
146
+ }
147
+ async replyToPost({ taskId, postId, text, preparedAction }) {
148
+ const executor = this.#executor;
149
+ if (preparedAction &&
150
+ (preparedAction.kind !== "reply" ||
151
+ preparedAction.postId !== postId ||
152
+ preparedAction.text !== text))
153
+ throw new Error("prepared-reply-target-mismatch");
154
+ await this.#prepareActionsForCall(taskId, preparedAction ? [preparedAction] : []);
155
+ return (executor ? this.#parallelRound : this.#round)({
156
+ taskId,
157
+ requestedKind: "reply",
158
+ selector: (a) => a.kind === "reply" && a.postId === postId && a.text === text,
159
+ });
160
+ }
161
+ }
162
+ export function createGrowthTasks(...args) {
163
+ return new GrowthTasks(...args);
164
+ }
package/dist/growth.d.ts CHANGED
@@ -1 +1,26 @@
1
- export declare function createGrowthHarness({ platform, store, clock, executor, }?: any): any;
1
+ /** Synchronous composition only; owners retain one original ledger and settlement path. */
2
+ export declare class GrowthHarness {
3
+ #private;
4
+ verifyPublishedPost: ({ taskId, draftPath, publicURL, evidenceRef, sourceLink, }: any) => Promise<any>;
5
+ verifySubmission: ({ taskId, actionId }: any) => Promise<any>;
6
+ verifyFollow: (target: any) => Promise<any>;
7
+ followBatch: ({ taskId, handles, preparedActions, }?: any) => Promise<import("./boundary.js").BoundaryRecord>;
8
+ preflight: ({ operation, input }: any) => any;
9
+ readCreator: (args: any) => Promise<any>;
10
+ inspectAccount: (args: any) => Promise<any>;
11
+ readPost: (args: any) => Promise<any>;
12
+ readPerson: (args: any) => Promise<any>;
13
+ readBookmarks: (args: any) => Promise<any>;
14
+ searchSources: (args: any) => Promise<any>;
15
+ readFeedback: (args: any) => Promise<any>;
16
+ runGrowthRound: (args: any) => Promise<any>;
17
+ resumeGrowthRound: (args: any) => Promise<any>;
18
+ inspectRound: ({ taskId }: any) => Promise<any>;
19
+ followOne: ({ taskId, handle }: any) => Promise<any>;
20
+ publishPost: (input: any) => Promise<any>;
21
+ replyToPost: ({ taskId, postId, text, preparedAction }: any) => Promise<any>;
22
+ constructor({ platform, store, clock, executor, }?: any);
23
+ private composeTasks;
24
+ private stamp;
25
+ }
26
+ export declare function createGrowthHarness(...args: ConstructorParameters<typeof GrowthHarness>): any;