@starci/hfs 4.0.10 → 4.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 (275) hide show
  1. package/CHANGELOG.md +5 -1
  2. package/emit/operations.mjs +1 -1
  3. package/emit/type-schema.mjs +2 -1
  4. package/lint/run.mjs +65 -24
  5. package/package.json +1 -1
  6. package/report/order.mjs +2 -0
  7. package/runtime/config.example.yaml +194 -0
  8. package/runtime/engine/by-code-unit.mjs +12 -0
  9. package/runtime/engine/config.mjs +222 -239
  10. package/runtime/engine/invalid-config.mjs +5 -5
  11. package/runtime/engine/model-config.mjs +90 -0
  12. package/runtime/engine/orca-config.mjs +3 -1
  13. package/runtime/engine/release-config.mjs +28 -0
  14. package/runtime/engine/removed-vocabulary.mjs +28 -0
  15. package/runtime/engine/resources-config.mjs +52 -0
  16. package/runtime/engine/runtime-root.mjs +17 -0
  17. package/runtime/engine/secrets.mjs +35 -31
  18. package/runtime/engine/sonar-config.mjs +21 -0
  19. package/runtime/engine/temp-root.mjs +32 -0
  20. package/runtime/knowledge/hfs/canon-pins.yaml +14 -14
  21. package/runtime/knowledge/hfs/peer-integrations.yaml +2 -3
  22. package/runtime/knowledge/hfs/rules.yaml +161 -65
  23. package/runtime/knowledge/hfs/slots.yaml +11 -14
  24. package/runtime/knowledge/patterns/be/api.yaml +7 -24
  25. package/runtime/knowledge/patterns/be/cli.yaml +3 -12
  26. package/runtime/knowledge/patterns/be/realtime.yaml +1 -14
  27. package/runtime/knowledge/patterns/be/webhooks.yaml +0 -19
  28. package/runtime/modules/kernel/failure-codes.yaml +1 -41
  29. package/runtime/modules/kernel/removed-vocabulary.yaml +268 -0
  30. package/runtime/modules/models/registry.yaml +25 -359
  31. package/runtime/modules/models/runtimes.yaml +126 -265
  32. package/runtime/modules/models/tiers.yaml +66 -0
  33. package/runtime/scripts/api/fs/claim-file.mjs +30 -0
  34. package/runtime/scripts/api/fs/ensure-temp-root.mjs +27 -0
  35. package/runtime/scripts/api/fs/forbidden-root.mjs +2 -1
  36. package/runtime/scripts/api/fs/make-temp-dir.mjs +11 -0
  37. package/runtime/scripts/api/fs/safe-remove.mjs +52 -35
  38. package/runtime/scripts/api/git/lib.mjs +34 -1
  39. package/runtime/scripts/api/process/resolve-real-tool.mjs +14 -8
  40. package/runtime/scripts/api/process/run-program.mjs +2 -1
  41. package/runtime/scripts/api/sops/lib.mjs +198 -120
  42. package/runtime/scripts/hfs/allows.mjs +3 -3
  43. package/runtime/scripts/hfs/architecture/ast-walks.mjs +63 -50
  44. package/runtime/scripts/hfs/architecture/automatic-gates.mjs +96 -0
  45. package/runtime/scripts/hfs/architecture/backend.mjs +261 -175
  46. package/runtime/scripts/hfs/architecture/background-unowned.mjs +69 -34
  47. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +55 -54
  48. package/runtime/scripts/hfs/architecture/clones.mjs +179 -106
  49. package/runtime/scripts/hfs/architecture/config-unread.mjs +5 -22
  50. package/runtime/scripts/hfs/architecture/config.mjs +95 -80
  51. package/runtime/scripts/hfs/architecture/connection-map.mjs +233 -141
  52. package/runtime/scripts/hfs/architecture/constructor-deps.mjs +15 -9
  53. package/runtime/scripts/hfs/architecture/context-coupling.mjs +76 -52
  54. package/runtime/scripts/hfs/architecture/context-map.mjs +215 -141
  55. package/runtime/scripts/hfs/architecture/context-owner.mjs +39 -30
  56. package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +31 -17
  57. package/runtime/scripts/hfs/architecture/context-transaction.mjs +38 -25
  58. package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +86 -40
  59. package/runtime/scripts/hfs/architecture/contracts-readonly.mjs +342 -0
  60. package/runtime/scripts/hfs/architecture/contracts.mjs +263 -455
  61. package/runtime/scripts/hfs/architecture/cross-app-duplicate.mjs +43 -34
  62. package/runtime/scripts/hfs/architecture/dead-exports.mjs +201 -154
  63. package/runtime/scripts/hfs/architecture/default-deny.mjs +118 -93
  64. package/runtime/scripts/hfs/architecture/doc-language.mjs +2 -1
  65. package/runtime/scripts/hfs/architecture/entrypoint.mjs +45 -30
  66. package/runtime/scripts/hfs/architecture/error-codes.mjs +19 -12
  67. package/runtime/scripts/hfs/architecture/error-masked.mjs +39 -26
  68. package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +2 -1
  69. package/runtime/scripts/hfs/architecture/feature-shape.mjs +26 -20
  70. package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
  71. package/runtime/scripts/hfs/architecture/frontend-grammar.mjs +190 -0
  72. package/runtime/scripts/hfs/architecture/frontend-routing.mjs +186 -0
  73. package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +311 -0
  74. package/runtime/scripts/hfs/architecture/frontend-world.mjs +328 -0
  75. package/runtime/scripts/hfs/architecture/frontend.mjs +109 -829
  76. package/runtime/scripts/hfs/architecture/hfs-graph.mjs +14 -2
  77. package/runtime/scripts/hfs/architecture/hfs.mjs +120 -288
  78. package/runtime/scripts/hfs/architecture/hooks-are-hooks.mjs +46 -26
  79. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +114 -76
  80. package/runtime/scripts/hfs/architecture/index.mjs +164 -104
  81. package/runtime/scripts/hfs/architecture/injection-token-exported.mjs +33 -28
  82. package/runtime/scripts/hfs/architecture/machine-ast.mjs +180 -144
  83. package/runtime/scripts/hfs/architecture/managed-scripts.mjs +8 -2
  84. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +150 -101
  85. package/runtime/scripts/hfs/architecture/next-data-contract.mjs +51 -43
  86. package/runtime/scripts/hfs/architecture/next-data-key.mjs +338 -0
  87. package/runtime/scripts/hfs/architecture/next-data.mjs +179 -427
  88. package/runtime/scripts/hfs/architecture/owners.mjs +27 -13
  89. package/runtime/scripts/hfs/architecture/package-shape.mjs +51 -23
  90. package/runtime/scripts/hfs/architecture/presentation.mjs +165 -0
  91. package/runtime/scripts/hfs/architecture/reachability.mjs +127 -61
  92. package/runtime/scripts/hfs/architecture/register-once.mjs +112 -63
  93. package/runtime/scripts/hfs/architecture/registration.mjs +187 -110
  94. package/runtime/scripts/hfs/architecture/required-files.mjs +121 -70
  95. package/runtime/scripts/hfs/architecture/route-files-thin.mjs +83 -58
  96. package/runtime/scripts/hfs/architecture/schema-owner.mjs +182 -108
  97. package/runtime/scripts/hfs/architecture/source-names-shape.mjs +318 -0
  98. package/runtime/scripts/hfs/architecture/source-names.mjs +83 -248
  99. package/runtime/scripts/hfs/architecture/sql-owner.mjs +175 -118
  100. package/runtime/scripts/hfs/architecture/sql-returning.mjs +39 -28
  101. package/runtime/scripts/hfs/architecture/sql-tokens.mjs +302 -172
  102. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +1 -1
  103. package/runtime/scripts/hfs/architecture/supabase-be.mjs +55 -45
  104. package/runtime/scripts/hfs/architecture/supabase-results.mjs +182 -0
  105. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +20 -12
  106. package/runtime/scripts/hfs/architecture/supabase.mjs +191 -263
  107. package/runtime/scripts/hfs/architecture/symbols.mjs +175 -99
  108. package/runtime/scripts/hfs/architecture/test-world-files.mjs +82 -41
  109. package/runtime/scripts/hfs/architecture/tiers.mjs +120 -69
  110. package/runtime/scripts/hfs/architecture/transport-owner.mjs +84 -57
  111. package/runtime/scripts/hfs/architecture/type-context.mjs +282 -0
  112. package/runtime/scripts/hfs/architecture/typescript.mjs +116 -276
  113. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +84 -62
  114. package/runtime/scripts/hfs/architecture.mjs +3 -4
  115. package/runtime/scripts/hfs/catalog-once.mjs +15 -0
  116. package/runtime/scripts/hfs/check.mjs +63 -71
  117. package/runtime/scripts/hfs/coverage-scope.mjs +84 -38
  118. package/runtime/scripts/hfs/declaration-shape.mjs +77 -36
  119. package/runtime/scripts/hfs/declaration-slots.mjs +1 -1
  120. package/runtime/scripts/hfs/edition-slots.mjs +5 -3
  121. package/runtime/scripts/hfs/linear-text.mjs +30 -0
  122. package/runtime/scripts/hfs/manifest-shape.mjs +164 -63
  123. package/runtime/scripts/hfs/path-findings.mjs +58 -38
  124. package/runtime/scripts/hfs/pin-findings.mjs +31 -0
  125. package/runtime/scripts/hfs/repo-identity.mjs +21 -4
  126. package/runtime/scripts/hfs/rule-catalog.mjs +194 -0
  127. package/runtime/scripts/hfs/rule-params-shape.mjs +33 -27
  128. package/runtime/scripts/hfs/rules/cli.mjs +5 -1
  129. package/runtime/scripts/hfs/rules/contract-compat.mjs +40 -22
  130. package/runtime/scripts/hfs/rules/contract.mjs +35 -27
  131. package/runtime/scripts/hfs/rules/database-config.mjs +57 -32
  132. package/runtime/scripts/hfs/rules/database-migrations.mjs +52 -31
  133. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +106 -91
  134. package/runtime/scripts/hfs/rules/database-sql.mjs +254 -173
  135. package/runtime/scripts/hfs/rules/database.mjs +36 -23
  136. package/runtime/scripts/hfs/rules/deps.mjs +64 -32
  137. package/runtime/scripts/hfs/rules/docker.mjs +113 -65
  138. package/runtime/scripts/hfs/rules/edition.mjs +118 -111
  139. package/runtime/scripts/hfs/rules/event-bus.mjs +91 -45
  140. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +17 -16
  141. package/runtime/scripts/hfs/rules/fe-no-tests.mjs +36 -16
  142. package/runtime/scripts/hfs/rules/frontend-tree.mjs +4 -3
  143. package/runtime/scripts/hfs/rules/integration-specs.mjs +75 -41
  144. package/runtime/scripts/hfs/rules/kinds.mjs +59 -25
  145. package/runtime/scripts/hfs/rules/lint-suppression.mjs +9 -3
  146. package/runtime/scripts/hfs/rules/monorepo.mjs +82 -45
  147. package/runtime/scripts/hfs/rules/peer-integrations.mjs +24 -3
  148. package/runtime/scripts/hfs/rules/pipeline.mjs +58 -9
  149. package/runtime/scripts/hfs/rules/proof-commands.mjs +14 -8
  150. package/runtime/scripts/hfs/rules/repo-local-checks.mjs +11 -6
  151. package/runtime/scripts/hfs/rules/saga.mjs +128 -61
  152. package/runtime/scripts/hfs/rules/secrets.mjs +4 -3
  153. package/runtime/scripts/hfs/rules/services.mjs +34 -16
  154. package/runtime/scripts/hfs/rules/stacks.mjs +21 -14
  155. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +32 -23
  156. package/runtime/scripts/hfs/rules/test-topology.mjs +25 -11
  157. package/runtime/scripts/hfs/secret.mjs +59 -48
  158. package/runtime/scripts/hfs/slot-app-view.mjs +102 -0
  159. package/runtime/scripts/hfs/slot-classify.mjs +65 -0
  160. package/runtime/scripts/hfs/slot-errors.mjs +11 -0
  161. package/runtime/scripts/hfs/slot-imports.mjs +76 -0
  162. package/runtime/scripts/hfs/slot-manifest-shape.mjs +73 -0
  163. package/runtime/scripts/hfs/slot-match.mjs +153 -0
  164. package/runtime/scripts/hfs/slot-path.mjs +8 -0
  165. package/runtime/scripts/hfs/slot-required.mjs +76 -0
  166. package/runtime/scripts/hfs/slot-semantic-problems.mjs +148 -0
  167. package/runtime/scripts/hfs/slot-side-problems.mjs +28 -0
  168. package/runtime/scripts/hfs/slots.mjs +74 -695
  169. package/runtime/scripts/hfs/sql/pg-parse.mjs +5 -2
  170. package/runtime/scripts/hfs/trailing-slashes.mjs +8 -0
  171. package/runtime/scripts/hfs/tree.mjs +19 -14
  172. package/runtime/scripts/hfs/typescript-programs.mjs +6 -4
  173. package/runtime/scripts/hfs/view.mjs +1 -1
  174. package/runtime/scripts/lib/dockerfile.mjs +56 -33
  175. package/runtime/scripts/lib/env.mjs +10 -0
  176. package/runtime/scripts/lib/event-contract.mjs +17 -13
  177. package/runtime/scripts/lib/git.mjs +2 -2
  178. package/runtime/scripts/lib/graphql-contract.mjs +158 -322
  179. package/runtime/scripts/lib/graphql-sdl.mjs +262 -0
  180. package/runtime/scripts/lib/i18n.mjs +3 -2
  181. package/runtime/scripts/lib/in-order.mjs +71 -0
  182. package/runtime/scripts/lib/language.mjs +3 -3
  183. package/runtime/scripts/lib/list.mjs +8 -1
  184. package/runtime/scripts/lib/path-key.mjs +38 -5
  185. package/runtime/scripts/lib/pid-alive.mjs +7 -0
  186. package/runtime/scripts/lib/regex.mjs +1 -1
  187. package/runtime/scripts/lib/same-text.mjs +1 -1
  188. package/runtime/scripts/lib/secret-patterns.mjs +6 -2
  189. package/runtime/scripts/lib/sleep-sync.mjs +2 -2
  190. package/runtime/scripts/lib/sops-envelope.mjs +95 -48
  191. package/runtime/scripts/lib/stack-services.mjs +1 -2
  192. package/runtime/scripts/lib/ts-ast.mjs +9 -0
  193. package/runtime/scripts/lib/walk.mjs +6 -1
  194. package/runtime/scripts/lib/yaml-cached.mjs +14 -0
  195. package/scaffold/add-cli-lite.mjs +2 -1
  196. package/scaffold/add-table.mjs +2 -2
  197. package/scaffold/add.mjs +3 -2
  198. package/scaffold/app.mjs +4 -2
  199. package/scaffold/edition-gate.mjs +25 -25
  200. package/scaffold/lite-exports.mjs +2 -1
  201. package/scaffold/service.mjs +5 -4
  202. package/sync/hygiene.mjs +15 -9
  203. package/sync/index.mjs +7 -3
  204. package/templates/app/ci-workflows/github/workflows/ci.yml +4 -4
  205. package/templates/app/ci-workflows/github/workflows/e2e.yml +3 -3
  206. package/templates/app/ci-workflows/github/workflows/images.yml +3 -3
  207. package/templates/app/ci-workflows/sonar-steps.yml +2 -2
  208. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +6 -6
  209. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +3 -3
  210. package/templates/app/ci-workflows-lite/github/workflows/images.yml +3 -3
  211. package/templates/app/starciwork.gitignore +1 -1
  212. package/templates/be/image/api/Dockerfile +1 -1
  213. package/templates/be/image/cli/Dockerfile +1 -1
  214. package/templates/be/image/worker/Dockerfile +1 -1
  215. package/templates/be/patterns/cli/group.cli.spec.ts.tpl +1 -1
  216. package/templates/be/patterns/cli/group.cli.ts.tpl +2 -2
  217. package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +13 -0
  218. package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +7 -2
  219. package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +4 -1
  220. package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +2 -1
  221. package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +24 -9
  222. package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +3 -2
  223. package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +8 -5
  224. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +1 -1
  225. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +2 -2
  226. package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +1 -1
  227. package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +2 -2
  228. package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +3 -2
  229. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +5 -4
  230. package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
  231. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +47 -0
  232. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.ts +16 -0
  233. package/templates/be/skeleton-lite/apps/api/Dockerfile +1 -0
  234. package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
  235. package/templates/fe/image/next/Dockerfile +2 -1
  236. package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +2 -2
  237. package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +2 -2
  238. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +1 -2
  239. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +1 -2
  240. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +2 -2
  241. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +7 -1
  242. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +1 -1
  243. package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +10 -2
  244. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +10 -3
  245. package/upgrade/index.mjs +9 -2
  246. package/runtime/engine/admission.mjs +0 -308
  247. package/runtime/engine/canonical-json.mjs +0 -10
  248. package/runtime/engine/db/blob.mjs +0 -315
  249. package/runtime/engine/db/ledger-paths.mjs +0 -83
  250. package/runtime/engine/db/ledger.mjs +0 -1118
  251. package/runtime/engine/db/machine-connection.mjs +0 -135
  252. package/runtime/engine/db/machine-schema.mjs +0 -84
  253. package/runtime/engine/db/machine.mjs +0 -1367
  254. package/runtime/engine/db/migrations/machine/0001-init.sql +0 -924
  255. package/runtime/engine/db/migrations/runtime/0001-init.sql +0 -1108
  256. package/runtime/engine/db/provider-reservations.mjs +0 -101
  257. package/runtime/engine/digest.mjs +0 -16
  258. package/runtime/engine/refuse.mjs +0 -11
  259. package/runtime/modules/ops/_labels.yaml +0 -53
  260. package/runtime/scripts/api/node/lib.mjs +0 -14
  261. package/runtime/scripts/api/node/spawn-node.mjs +0 -6
  262. package/runtime/scripts/api/process/lib.mjs +0 -111
  263. package/runtime/scripts/api/process/owned-process.mjs +0 -78
  264. package/runtime/scripts/api/process/stop-owned-process.mjs +0 -9
  265. package/runtime/scripts/connectors/lib.mjs +0 -488
  266. package/runtime/scripts/lib/clip.mjs +0 -19
  267. package/runtime/scripts/lib/display-names.mjs +0 -259
  268. package/runtime/scripts/lib/example-refs.mjs +0 -158
  269. package/runtime/scripts/lib/json-schema.mjs +0 -52
  270. package/runtime/scripts/lib/process-identity.mjs +0 -6
  271. package/runtime/scripts/lib/read-yaml.mjs +0 -18
  272. package/runtime/scripts/lib/redact.mjs +0 -161
  273. package/runtime/scripts/lib/sleep.mjs +0 -7
  274. package/runtime/scripts/lib/source-phrases.mjs +0 -34
  275. package/runtime/scripts/lib/sqlite.mjs +0 -21
@@ -1,101 +0,0 @@
1
- // Provider receipt SQL participates in the machine writer's existing fenced transactions.
2
-
3
- export function providerReservationMethods({ need, parse, toJson, hex }) {
4
- // Provider/account slots are shared by all five agent roles. A clock timeout is not exit evidence.
5
- const providerReservationRow = (row) => row ? { id: row.id, fence: row.fence, attemptId: row.attempt_id,
6
- provider: row.provider, account: row.account, model: row.model, role: row.role, state: row.state,
7
- slots: row.slots, amountUnit: 'concurrent-launch', maxParallel: row.max_parallel,
8
- handle: row.handle, pid: row.pid, launchIdentity: row.launch_identity, hostRequestId: row.host_request_id, createdAt: row.created_at, updatedAt: row.updated_at,
9
- scope: parse(row.scope_json), estimate: parse(row.estimate_json), quota: parse(row.quota_json),
10
- override: parse(row.override_json), releasedAt: row.released_at, proof: parse(row.proof_json) } : null;
11
- function providerReservations(m, { provider = null, account = null, activeOnly = false } = {}) {
12
- const where = [], args = [];
13
- if (provider !== null) { where.push('provider=?'); args.push(provider); }
14
- if (account !== null) { where.push('account=?'); args.push(account); }
15
- if (activeOnly) where.push("state<>'released'");
16
- return m.db.prepare(`SELECT * FROM provider_reservations${where.length ? ` WHERE ${where.join(' AND ')}` : ''} ORDER BY fence`).all(...args).map(providerReservationRow);
17
- }
18
- function providerReservationUsage(m, { provider, account = 'default' } = {}) {
19
- const reservations = providerReservations(m, { provider, account, activeOnly: true });
20
- return { running: reservations.length, reservations, observed: true };
21
- }
22
- /** Atomic last-slot admission. Replays preserve the original receipt; changed attempt meaning refuses. */
23
- function reserveProvider(m, { provider, account = 'default', attemptId, role, model, maxParallel, scope = null, quota = null, estimate = null, override = null } = {}) {
24
- need([provider, account, attemptId, model].every((value) => typeof value === 'string' && value.trim()), 'provider reservation needs provider, account, attemptId and concrete model');
25
- need(typeof role === 'string' && role.trim(), 'provider reservation needs one agent role'); // The DDL owns the allowed role domain.
26
- need(Number.isInteger(maxParallel) && maxParallel >= 0, 'provider reservation needs nonnegative integer maxParallel');
27
- return m.transaction((db) => {
28
- const existing = db.prepare('SELECT * FROM provider_reservations WHERE attempt_id=?').get(attemptId);
29
- if (existing) {
30
- if (existing.provider !== provider || existing.account !== account || existing.model !== model || existing.role !== role || existing.scope_json !== toJson(scope))
31
- return { ok: false, reason: 'attempt-conflict', reservation: providerReservationRow(existing) };
32
- return { ok: existing.state !== 'released', reason: existing.state === 'released' ? 'attempt-released' : 'reserved', reused: true, reservation: providerReservationRow(existing) };
33
- }
34
- const running = Number(db.prepare("SELECT count(*) n FROM provider_reservations WHERE provider=? AND account=? AND state<>'released'").get(provider, account).n);
35
- if (running >= maxParallel) return { ok: false, reason: 'capacity', running, maxParallel };
36
- const id = hex(16), at = m.now();
37
- const result = db.prepare("INSERT INTO provider_reservations(id,attempt_id,provider,account,model,role,scope_json,state,max_parallel,quota_json,estimate_json,override_json,created_at,updated_at) VALUES(?,?,?,?,?,?,?,'reserved',?,?,?,?,?,?)")
38
- .run(id, attemptId, provider, account, model, role, toJson(scope), maxParallel, toJson(quota), toJson(estimate), toJson(override), at, at);
39
- db.prepare("INSERT INTO provider_reservation_events(reservation_id,at,from_state,to_state) VALUES(?,?,NULL,'reserved')").run(id, at);
40
- return { ok: true, reason: 'reserved', reused: false, reservation: providerReservationRow(db.prepare('SELECT * FROM provider_reservations WHERE fence=?').get(result.lastInsertRowid)) };
41
- });
42
- }
43
- const fencedProviderRow = (db, { id, fence, attemptId } = {}) => {
44
- const row = db.prepare('SELECT * FROM provider_reservations WHERE id=? AND fence=?').get(id ?? '', fence ?? -1);
45
- return row && (attemptId === undefined || row.attempt_id === attemptId) ? row : null;
46
- };
47
- /** Observe launch progress under its original fence; unknown still occupies its slot. */
48
- function markProviderReservation(m, { id, fence, attemptId, provider, account, model, role, state, launchIdentity = null, hostRequestId = null, handle = null, pid = null } = {}) {
49
- need(['launching', 'live', 'unknown'].includes(state), 'provider reservation observation needs launching, live or unknown');
50
- return m.transaction((db) => {
51
- const row = fencedProviderRow(db, { id, fence, attemptId });
52
- if (!row) return { ok: false, reason: 'fenced' };
53
- if (Object.entries({ provider, account, model, role }).some(([key, value]) => value !== undefined && row[key] !== value)) return { ok: false, reason: 'identity-conflict' };
54
- if (row.state === 'released') return { ok: false, reason: 'released' };
55
- if (state === 'launching' && (typeof launchIdentity !== 'string' || !launchIdentity.trim())) return { ok: false, reason: 'launch-identity-required' };
56
- if (launchIdentity !== null && row.launch_identity && row.launch_identity !== launchIdentity) return { ok: false, reason: 'launch-identity-conflict', reservation: providerReservationRow(row) };
57
- if (hostRequestId !== null && row.host_request_id && row.host_request_id !== hostRequestId) return { ok: false, reason: 'host-request-conflict', reservation: providerReservationRow(row) };
58
- if (state === 'launching' && row.state !== 'reserved' && row.state !== 'launching') return { ok: false, reason: 'state-regression' };
59
- if ((row.handle && handle && row.handle !== handle) || (row.pid && pid && row.pid !== pid)) return { ok: false, reason: 'identity-conflict' };
60
- const at = m.now();
61
- db.prepare('UPDATE provider_reservations SET state=?,launch_identity=coalesce(launch_identity,?),host_request_id=coalesce(host_request_id,?),handle=coalesce(handle,?),pid=coalesce(pid,?),updated_at=? WHERE id=? AND fence=?')
62
- .run(state, launchIdentity, hostRequestId, handle, pid, at, id, fence);
63
- if (row.state !== state) db.prepare('INSERT INTO provider_reservation_events(reservation_id,at,from_state,to_state) VALUES(?,?,?,?)').run(id, at, row.state, state);
64
- return { ok: true, reservation: providerReservationRow(db.prepare('SELECT * FROM provider_reservations WHERE id=?').get(id)) };
65
- });
66
- }
67
- /** Only the launcher/reconciler's definitive matching exit/no-effect receipt releases capacity. */
68
- function releaseProviderReservation(m, { id, fence, attemptId, proof } = {}) {
69
- return m.transaction((db) => {
70
- const row = fencedProviderRow(db, { id, fence, attemptId });
71
- if (!row) return { ok: false, reason: 'fenced' };
72
- if (row.state === 'released') return { ok: true, reused: true, reservation: providerReservationRow(row) };
73
- const affirmation = proof?.hostAffirmation;
74
- const reply = affirmation?.receipt, result = reply?.result;
75
- const residual = result?.residualResources ?? result?.worker?.residualResources;
76
- const identities = [result, result?.worker, result?.terminal].filter((value) => value && typeof value === 'object');
77
- const hostNoEffect = reply && typeof reply === 'object' && !['unknown', 'outcome_unknown'].includes(result?.state)
78
- && !['unknown', 'outcome_unknown'].includes(result?.worker?.state)
79
- && !(residual && typeof residual === 'object' && Object.keys(residual).length > 0)
80
- && !identities.some((value) => ['agentTerminalHandle', 'terminalHandle', 'handle', 'pid', 'processId', 'dispatchId'].some((key) => value[key] !== undefined && value[key] !== null && value[key] !== ''))
81
- && (result?.failedStage !== undefined || result?.stage !== undefined || typeof reply.error?.code === 'string' && reply.error.code.trim());
82
- const resolvedUnknown = row.state === 'unknown' && row.launch_identity && proof?.launchIdentity === row.launch_identity
83
- && row.host_request_id && affirmation?.requestId === row.host_request_id && affirmation.kind === 'worker-start-replay'
84
- && affirmation.replayed === true && affirmation.outcome === 'failed' && affirmation.effectState === 'none'
85
- && hostNoEffect;
86
- const noEffect = proof?.kind === 'failed-before-launch' && proof.confirmed === true && !row.handle && !row.pid
87
- && (['reserved', 'launching'].includes(row.state) || resolvedUnknown);
88
- const terminalEnded = row.handle && proof?.kind === 'closed' && proof.handle === row.handle
89
- && ['gone', 'disconnected'].includes(proof.terminalProof) && ['none', 'stopped'].includes(proof.processVerdict);
90
- const processEnded = !row.handle && row.pid && proof?.kind === 'process-exited' && proof.pid === row.pid;
91
- const ended = proof?.confirmed === true && (terminalEnded || processEnded)
92
- && (!row.pid || proof.pid == null || proof.pid === row.pid);
93
- if (!noEffect && !ended) return { ok: false, reason: 'exit-unproven', reservation: providerReservationRow(row) };
94
- const at = m.now();
95
- db.prepare("UPDATE provider_reservations SET state='released',released_at=?,updated_at=?,proof_json=? WHERE id=? AND fence=?").run(at, at, toJson(proof), id, fence);
96
- db.prepare("INSERT INTO provider_reservation_events(reservation_id,at,from_state,to_state,proof_json) VALUES(?,?,?,'released',?)").run(id, at, row.state, toJson(proof));
97
- return { ok: true, reused: false, reservation: providerReservationRow(db.prepare('SELECT * FROM provider_reservations WHERE id=?').get(id)) };
98
- });
99
- }
100
- return { reserveProvider, providerReservations, providerReservationUsage, markProviderReservation, releaseProviderReservation };
101
- }
@@ -1,16 +0,0 @@
1
- // digest.mjs — the one SHA-256 the runtime hashes with. A leaf: every file's integrity digest,
2
- // every ledger input ref and every stamped asset is this function, so a byte stream and a file
3
- // never disagree about what their digest is.
4
- import { createHash } from 'node:crypto';
5
- import fs from 'node:fs';
6
-
7
- /** The lowercase hex SHA-256 of `value` (string, Buffer or TypedArray). */
8
- export const sha256 = (value) => createHash('sha256').update(value).digest('hex');
9
- /** The SHA-256 of the file's exact bytes; throws when the file cannot be read. */
10
- export function sha256File(file){
11
- // A numeric descriptor belongs to the caller; positional reads preserve its offset.
12
- const owned=typeof file!=='number',fd=owned?fs.openSync(file,'r'):file;
13
- const hash=createHash('sha256'),buffer=Buffer.alloc(1024*1024);
14
- try{let at=0,n;while((n=fs.readSync(fd,buffer,0,buffer.length,at))>0){hash.update(buffer.subarray(0,n));at+=n;}return hash.digest('hex');}
15
- finally{if(owned)fs.closeSync(fd);}
16
- }
@@ -1,11 +0,0 @@
1
- // refuse.mjs — the refusal Error every verb raises: a message plus a stable `code` (and any extra fields the
2
- // caller attaches). `refuse` builds it, `fail` throws it, `need` throws it when a required condition is false.
3
-
4
- /** An Error carrying `code` and `extra` fields; callers `throw` it or hand it to a result row. */
5
- export const refuse = (message, code, extra = {}) => Object.assign(new Error(message), { code, ...extra });
6
-
7
- /** Throws the refusal `refuse` builds — the spelling used where refusing is the whole statement. */
8
- export const fail = (message, code, extra = {}) => { throw refuse(message, code, extra); };
9
-
10
- /** Throws `refuse(message, code)` when `ok` is falsy — the assertion spelling of a refusal. */
11
- export const need = (ok, message, code) => { if (!ok) throw refuse(message, code); };
@@ -1,53 +0,0 @@
1
- # _labels.yaml — the human name of every operation, one shared map.
2
- # The underscore prefix keeps this file out of the op catalog scan, like
3
- # _common.yaml: scripts/route/build-ops-registry.mjs and route-op.mjs skip
4
- # modules/ops/*.yaml files whose names start with "_".
5
- #
6
- # Owner request 2026-09-27: kernels, workflows and op workers carry names a person understands. The
7
- # op id (interface.draw) stays the key everywhere — the ledger, the api, job ids, contracts; this map
8
- # only says what a human reads for it. Every reader goes through scripts/lib/display-names.mjs
9
- # (opLabel): Orca [Op] worker titles, starci kernel status legs/nextActions `label`, the supervisor digest and
10
- # progress report, Telegram notices and the harness UI (ui/server.mjs sends the map in the snapshot
11
- # as `opLabels`, so the UI shows exactly these words). tests/lib/display-names.spec.mjs keeps one entry per
12
- # modules/ops/ops/*.yaml. `vi` is what the owner reads; `en` is the UI's English rendering of it.
13
- schema: starci/op-labels@1
14
-
15
- labels:
16
- request.analyze: { vi: Phân tích yêu cầu, en: Analyze request }
17
- task.execute: { vi: Chọn luồng việc, en: Select workflow }
18
- scope.define: { vi: Xác định phạm vi, en: Define scope }
19
- scope.finish: { vi: Kết thúc phạm vi, en: Finish scope }
20
- goal.revise: { vi: Sửa mục tiêu, en: Revise goal }
21
- business.decide: { vi: Chốt nghiệp vụ, en: Decide business rules }
22
- architecture.decide: { vi: Thiết kế kiến trúc, en: Design architecture }
23
- brand.decide: { vi: Chốt thương hiệu, en: Decide brand }
24
- decision.prepare: { vi: Chuẩn bị quyết định, en: Prepare decision }
25
- provision.ask: { vi: Xin thông tin, en: Request information }
26
- work.author: { vi: Chia việc chi tiết, en: Plan work }
27
- workspace.manage: { vi: Dựng workspace, en: Manage workspace }
28
- knowledge.repair: { vi: Sửa tri thức, en: Repair knowledge }
29
- grammar.update: { vi: Bổ sung Grammar, en: Update grammar }
30
- interface.draw: { vi: Vẽ giao diện, en: Draw interface }
31
- interface.asset: { vi: Làm hình ảnh, en: Make assets }
32
- interface.scaffold: { vi: Dựng khung giao diện, en: Scaffold interface }
33
- interface.implement: { vi: Code giao diện, en: Implement interface }
34
- interface.audit: { vi: Soát giao diện, en: Audit interface }
35
- backend.scaffold: { vi: Dựng khung backend, en: Scaffold backend }
36
- backend.implement: { vi: Code backend, en: Implement backend }
37
- package.scaffold: { vi: Dựng khung package, en: Scaffold package }
38
- code.refactor: { vi: Chỉnh sửa mã nguồn, en: Refactor code }
39
- content.generate: { vi: Soạn nội dung, en: Generate content }
40
- docs.author: { vi: Viết tài liệu, en: Write docs }
41
- test.author: { vi: Viết test, en: Write tests }
42
- integration.verify: { vi: Kiểm thử tích hợp, en: Integration test }
43
- e2e.verify: { vi: Kiểm thử đầu cuối, en: End-to-end test }
44
- perf.verify: { vi: Kiểm thử hiệu năng, en: Performance test }
45
- unit.verify: { vi: Chạy toàn bộ unit test, en: Full unit test run }
46
- security.verify: { vi: Soát bảo mật, en: Security review }
47
- uat.verify: { vi: Kiểm thử UAT, en: UAT test }
48
- uat.assisted.prepare: { vi: Chuẩn bị UAT có người, en: Prepare assisted UAT }
49
- uat.assisted.verify: { vi: Xác minh UAT có người, en: Verify assisted UAT }
50
- review.verify: { vi: Review cuối, en: Final review }
51
- handover.review: { vi: Bàn giao, en: Handover }
52
- release.deliver: { vi: Phát hành, en: Release }
53
- runtime.operate: { vi: Vận hành môi trường, en: Operate runtime }
@@ -1,14 +0,0 @@
1
- // scripts/api/node/lib.mjs — the runner of the runtime's own scripts as child processes: the node binary that runs this
2
- // process (process.execPath), a hidden window, utf8 text. The call files beside it (run-script.mjs, syntax-check.mjs,
3
- // run-node.mjs, spawn-node.mjs, exec-node.mjs) each name one use; nothing outside scripts/api/node imports this runner.
4
- import { assertMutationFence } from '../../lib/mutation-fence.mjs';
5
- import { execFile, spawn, spawnSync } from 'node:child_process';
6
-
7
- /** `node <args>` with this process's node binary, waited for; options pass through last (cwd, stdio, env, timeout, maxBuffer, input). */
8
- export const nodeSpawn = (args, options = {}) => { assertMutationFence({ kind: 'node-effect', args }); return spawnSync(process.execPath, args, { encoding: 'utf8', windowsHide: true, ...options }); };
9
-
10
- /** `node <args>` with this process's node binary, started and not waited for: the ChildProcess (detached, stdio, env pass through). */
11
- export const nodeStart = (args, options = {}) => { assertMutationFence({ kind: 'node-effect', args }); return spawn(process.execPath, args, { windowsHide: true, ...options }); };
12
-
13
- /** `node <args>` with this process's node binary, waited for without blocking the thread: callback(error, stdout, stderr). */
14
- export const nodeExecFile = (args, options, callback) => { assertMutationFence({ kind: 'node-effect', args }); return execFile(process.execPath, args, { encoding: 'utf8', windowsHide: true, ...options }, callback); };
@@ -1,6 +0,0 @@
1
- // spawn-node.mjs — `node <args>` with this process's node binary, started and not waited for: a long-running runtime
2
- // process (a server, an engine, a detached helper). Returns the ChildProcess.
3
- import { nodeStart } from './lib.mjs';
4
-
5
- /** Starts `node <args>`; options (cwd, env, stdio, detached) pass through. */
6
- export const spawnNode = (args, options = {}) => nodeStart(args, options);
@@ -1,111 +0,0 @@
1
- // scripts/api/process/lib.mjs — what the process call files beside it share: the spawn options every child of the
2
- // runtime carries. The call files (process-list.mjs, kill-tree.mjs, run-shell.mjs, spawn-detached.mjs, set-priority.mjs,
3
- // hide-child-windows.mjs, ...) each name one use of the host's process table or a child process.
4
-
5
- /**
6
- * `list` with windowsHide: true added to its options argument. `hasArgv`:
7
- * the call's second argument may be an argv array (spawn, execFile) rather
8
- * than options (exec). An explicit windowsHide (true or false) is kept.
9
- */
10
- export function withWindowsHide(list, hasArgv) {
11
- const out = [...list];
12
- let slot = 1;
13
- if (hasArgv && (Array.isArray(out[1]) || (out[1] == null && out.length > 2))) slot = 2;
14
- const options = out[slot];
15
- if (options == null) out.splice(slot, out.length > slot ? 1 : 0, { windowsHide: true });
16
- else if (typeof options === 'function') out.splice(slot, 0, { windowsHide: true });
17
- else if (typeof options === 'object' && !('windowsHide' in options)) out[slot] = { ...options, windowsHide: true };
18
- return out;
19
- }
20
-
21
- // ---- the host process table (process-list.mjs, process-list-async.mjs) ------------------------------------------
22
-
23
- const psQuote = (s) => String(s).replace(/'/g, "''");
24
-
25
- /** The PowerShell script of one process-table read: Win32_Process rows (optionally filtered), with CPU when asked. */
26
- export function processListScript({ where = null, cmdMax = 4000, cpu = false } = {}) {
27
- const filter = where ? ` -Filter '${psQuote(where)}'` : '';
28
- return [
29
- ...(cpu ? ['$perf = @{}', 'Get-CimInstance Win32_PerfFormattedData_PerfProc_Process | ForEach-Object { if ($_.IDProcess -gt 0) { $perf[[int]$_.IDProcess] = [double]$_.PercentProcessorTime } }'] : []),
30
- `Get-CimInstance Win32_Process${filter} | ForEach-Object { $c = [string]$_.CommandLine; [pscustomobject]@{ pid = [int]$_.ProcessId; ppid = [int]$_.ParentProcessId; name = [string]$_.Name; exe = [string]$_.ExecutablePath;`,
31
- ` cmd = $c.Substring(0, [Math]::Min(${Number(cmdMax) || 4000}, $c.Length)); ws = [int64]$_.WorkingSetSize;`,
32
- ` created = $(if ($_.CreationDate) { ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() } else { 0 })${cpu ? '; cpu = $perf[[int]$_.ProcessId]' : ''} } } | ConvertTo-Json -Compress`,
33
- ].join('\n');
34
- }
35
-
36
- /** The rows of the PowerShell read's JSON (one object or an array). */
37
- export const processRowsOfJson = (text) => {
38
- const t = String(text ?? '').trim();
39
- const v = t ? JSON.parse(t) : [];
40
- return (Array.isArray(v) ? v : [v]).map((p) => ({ ...p, pid: Number(p.pid), ppid: Number(p.ppid), cmd: String(p.cmd ?? '') }));
41
- };
42
-
43
- /** The rows of `ps -eo pid=,ppid=,comm=,args=` (exe/created/ws are null there). */
44
- export const processRowsOfPs = (text, cmdMax = 4000) => String(text ?? '').split(/\r?\n/).map((line) => /^\s*(\d+)\s+(\d+)\s+(\S+)\s+(.*)$/.exec(line)).filter(Boolean)
45
- .map((m) => ({ pid: Number(m[1]), ppid: Number(m[2]), name: m[3].split('/').pop(), exe: null, cmd: m[4].slice(0, cmdMax), created: null, ws: null }));
46
-
47
- // ---- the environment of other processes (process-env.mjs) -------------------------------------------------------
48
-
49
- // Windows keeps a process's environment block in its PEB: PEB.ProcessParameters (+0x20) -> Environment (+0x80) and
50
- // EnvironmentSize (+0x3F0), x64 only. A same-user process opens with PROCESS_QUERY_INFORMATION | PROCESS_VM_READ (0x0410).
51
- export const PROCESS_ENV_NATIVE = `
52
- using System;
53
- using System.Collections.Generic;
54
- using System.Runtime.InteropServices;
55
- using System.Text;
56
- public static class StarciProcessEnv {
57
- [StructLayout(LayoutKind.Sequential)] struct PBI { public IntPtr Reserved1; public IntPtr PebBaseAddress; public IntPtr Reserved2a; public IntPtr Reserved2b; public IntPtr UniqueProcessId; public IntPtr Reserved3; }
58
- [DllImport("ntdll.dll")] static extern int NtQueryInformationProcess(IntPtr h, int cls, ref PBI pbi, int len, out int ret);
59
- [DllImport("kernel32.dll")] static extern bool ReadProcessMemory(IntPtr h, IntPtr addr, byte[] buf, IntPtr size, out IntPtr read);
60
- [DllImport("kernel32.dll")] static extern IntPtr OpenProcess(int access, bool inherit, int pid);
61
- [DllImport("kernel32.dll")] static extern bool CloseHandle(IntPtr h);
62
- [DllImport("kernel32.dll")] static extern bool IsWow64Process(IntPtr h, out bool wow);
63
- static byte[] Read(IntPtr h, IntPtr addr, int size) {
64
- var buf = new byte[size]; IntPtr n;
65
- if (!ReadProcessMemory(h, addr, buf, (IntPtr)size, out n) || (int)n != size) return null;
66
- return buf;
67
- }
68
- public static Dictionary<string, string> Env(int pid) {
69
- var h = OpenProcess(0x0410, false, pid);
70
- if (h == IntPtr.Zero) return null;
71
- try { return EnvHandle(h); } finally { CloseHandle(h); }
72
- }
73
- public static Dictionary<string, string> EnvHandle(IntPtr h) {
74
- bool wow; if (IsWow64Process(h, out wow) && wow) return null;
75
- var pbi = new PBI(); int ret;
76
- if (NtQueryInformationProcess(h, 0, ref pbi, Marshal.SizeOf(pbi), out ret) != 0) return null;
77
- var peb = Read(h, pbi.PebBaseAddress + 0x20, 8); if (peb == null) return null;
78
- var parameters = (IntPtr)BitConverter.ToInt64(peb, 0);
79
- var envPtr = Read(h, parameters + 0x80, 8); if (envPtr == null) return null;
80
- var sizeBuf = Read(h, parameters + 0x3F0, 8); if (sizeBuf == null) return null;
81
- long size = BitConverter.ToInt64(sizeBuf, 0);
82
- if (size <= 0 || size > 4 * 1024 * 1024) return null;
83
- var block = Read(h, (IntPtr)BitConverter.ToInt64(envPtr, 0), (int)size); if (block == null) return null;
84
- var d = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
85
- foreach (var entry in Encoding.Unicode.GetString(block).Split('\\0')) {
86
- if (entry.Length == 0) continue;
87
- int i = entry.IndexOf('=', 1); if (i < 0) continue;
88
- d[entry.Substring(0, i)] = entry.Substring(i + 1);
89
- }
90
- return d;
91
- }
92
- }`;
93
-
94
- /** The PowerShell script reading `names` from the environment of `pids` (every process when null): JSON [{pid, readable, values}]. */
95
- export function processEnvScript({ names, pids = null }) {
96
- const wanted = JSON.stringify([...names].map(String));
97
- const ids = pids ? `@(${[...pids].map((p) => Number(p)).filter(Number.isInteger).join(',')})` : '(Get-Process).Id';
98
- return [
99
- `Add-Type -TypeDefinition @'${PROCESS_ENV_NATIVE}\n'@ -Language CSharp`,
100
- `$names = '${psQuote(wanted)}' | ConvertFrom-Json`,
101
- `$rows = foreach ($id in ${ids}) { $e = [StarciProcessEnv]::Env([int]$id); $v = [ordered]@{}; foreach ($n in $names) { $v[$n] = if ($null -ne $e -and $e.ContainsKey($n)) { $e[$n] } else { $null } }; [pscustomobject]@{ pid = [int]$id; readable = ($null -ne $e); values = $v } }`,
102
- '@($rows) | ConvertTo-Json -Compress -Depth 4',
103
- ].join('\n');
104
- }
105
-
106
- /** The rows of that script's JSON. */
107
- export const processEnvRowsOfJson = (text) => {
108
- const t = String(text ?? '').trim();
109
- const v = t ? JSON.parse(t) : [];
110
- return (Array.isArray(v) ? v : [v]).map((r) => ({ pid: Number(r.pid), readable: r.readable === true, values: r.values ?? {} }));
111
- };
@@ -1,78 +0,0 @@
1
- // Process-object identity and bounded termination; callers supply custody captured at their own launch.
2
- import { spawnSync } from 'node:child_process';
3
- import { PROCESS_ENV_NATIVE } from './lib.mjs';
4
- import { OWNED_PROCESS_SCHEMA, validProcessIdentity as validIdentity } from '../../lib/process-identity.mjs';
5
- import { allocationMs } from '../../../engine/config.mjs';
6
-
7
- const NATIVE = `
8
- public static class StarciOwnedProcess {
9
- [DllImport("kernel32.dll", SetLastError=true)] static extern IntPtr OpenProcess(uint access, bool inherit, int pid);
10
- [DllImport("kernel32.dll", SetLastError=true)] static extern bool GetProcessTimes(IntPtr h, out long created, out long exited, out long kernel, out long user);
11
- [DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)] static extern bool QueryFullProcessImageName(IntPtr h, uint flags, StringBuilder exe, ref int size);
12
- [DllImport("kernel32.dll", SetLastError=true)] static extern bool TerminateProcess(IntPtr h, uint code);
13
- [DllImport("kernel32.dll", SetLastError=true)] static extern uint WaitForSingleObject(IntPtr h, uint ms);
14
- [DllImport("kernel32.dll")] static extern bool CloseHandle(IntPtr h);
15
- public static object[] Call(int pid, bool stop, string birth, string image, string ownershipKey, string ownershipValue, uint waitMs) {
16
- var h = OpenProcess(0x00101000u | (stop ? 1u : 0u) | (ownershipKey != null ? 0x0410u : 0u), false, pid);
17
- if (h == IntPtr.Zero) return new object[] {false, "unknown", "process-open-unverified", null, null, Marshal.GetLastWin32Error()};
18
- try {
19
- long created, exited, kernel, user; var exe = new StringBuilder(32768); int size = exe.Capacity;
20
- if (!GetProcessTimes(h, out created, out exited, out kernel, out user) || !QueryFullProcessImageName(h, 0, exe, ref size))
21
- return new object[] {false, "unknown", "process-identity-unreadable", null, null, Marshal.GetLastWin32Error()};
22
- var actualBirth = created.ToString(System.Globalization.CultureInfo.InvariantCulture); var actualImage = exe.ToString();
23
- if (stop && (actualBirth != birth || !String.Equals(actualImage, image, StringComparison.OrdinalIgnoreCase)))
24
- return new object[] {false, "refused", "process-identity-conflict", actualBirth, actualImage, 0};
25
- if (!stop && ownershipKey != null) {
26
- var environment = StarciProcessEnv.EnvHandle(h);
27
- if (environment == null || !environment.ContainsKey(ownershipKey) || environment[ownershipKey] != ownershipValue)
28
- return new object[] {false, "unknown", "process-launch-custody-unverified", actualBirth, actualImage, 0};
29
- }
30
- var first = WaitForSingleObject(h, 0);
31
- if (!stop) return first == 258u ? new object[] {true, "captured", "process-handle-live", actualBirth, actualImage, 0}
32
- : new object[] {false, "unknown", "process-not-live-at-capture", actualBirth, actualImage, 0};
33
- if (first == 0u) return new object[] {true, "gone", "process-handle-signaled", actualBirth, actualImage, 0};
34
- if (first != 258u || !TerminateProcess(h, 1))
35
- return new object[] {false, "unknown", "process-termination-unverified", actualBirth, actualImage, Marshal.GetLastWin32Error()};
36
- return WaitForSingleObject(h, waitMs) == 0u
37
- ? new object[] {true, "stopped", "process-handle-signaled", actualBirth, actualImage, 0}
38
- : new object[] {false, "unknown", "process-closure-unverified", actualBirth, actualImage, 0};
39
- } finally { CloseHandle(h); }
40
- }
41
- }`;
42
- const quote = (value) => value == null ? '$null' : `'${String(value).replaceAll("'", "''")}'`;
43
-
44
- /**
45
- * Capture a live process, or stop the exact captured identity on one verified native handle.
46
- * Only Windows is qualified; custody, platform, child completion or receipt failures remain refused or unknown.
47
- * The capture and stop call files constrain their modes; this engine preserves the supplied identity and raw outcome.
48
- */
49
- export function ownedProcess(pid, { identity = null, ownership = null, waitMs = allocationMs('workerClose.stopVerifyMs'), run = spawnSync, platform = process.platform } = {}) {
50
- const base = { schema: OWNED_PROCESS_SCHEMA, pid, ok: false, outcome: 'unknown', identity: identity ?? null };
51
- if (platform !== 'win32') return { ...base, reason: 'process-platform-unverified' };
52
- if (!Number.isInteger(pid) || pid <= 0 || identity && !validIdentity(identity)
53
- || ownership && (typeof ownership.key !== 'string' || !ownership.key || typeof ownership.value !== 'string' || !ownership.value))
54
- return { ...base, outcome: 'refused', reason: 'process-custody-required' };
55
- if (!Number.isInteger(waitMs) || waitMs <= 0 || waitMs > 60000) return { ...base, outcome: 'refused', reason: 'process-wait-invalid' };
56
- const script = [`Add-Type -TypeDefinition @'\n${PROCESS_ENV_NATIVE}\n${NATIVE}\n'@ -Language CSharp`,
57
- `$r = [StarciOwnedProcess]::Call(${pid}, $${identity ? 'true' : 'false'}, ${quote(identity?.birth)}, ${quote(identity?.exe)}, ${quote(ownership?.key)}, ${quote(ownership?.value)}, ${waitMs})`,
58
- `@{ schema = '${OWNED_PROCESS_SCHEMA}'; pid = ${pid}; ok = $r[0]; outcome = $r[1]; proof = $r[2]; birth = $r[3]; exe = $r[4]; nativeError = $r[5] } | ConvertTo-Json -Compress`].join('\n');
59
- let result;
60
- try { result = run('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script],
61
- { encoding: 'utf8', windowsHide: true, timeout: waitMs + 15000, maxBuffer: 1024 * 1024 }); }
62
- catch (error) { return { ...base, reason: 'process-call-incomplete', error: String(error?.message ?? error) }; }
63
- if (result?.status !== 0 || result.error || result.signal)
64
- return { ...base, reason: 'process-call-incomplete', status: result?.status ?? null, signal: result?.signal ?? null, error: result?.error?.message ?? null };
65
- let data;
66
- try { data = JSON.parse(String(result.stdout ?? '').trim()); } catch { return { ...base, reason: 'process-receipt-incomplete' }; }
67
- const actual = { pid, birth: data?.birth, exe: data?.exe };
68
- if (data?.schema !== OWNED_PROCESS_SCHEMA || data.pid !== pid || typeof data.ok !== 'boolean'
69
- || !['unknown', 'refused', 'captured', 'stopped', 'gone'].includes(data.outcome) || typeof data.proof !== 'string'
70
- || data.ok && (!validIdentity(actual) || identity && (actual.birth !== identity.birth || actual.exe.toLowerCase() !== identity.exe.toLowerCase())
71
- || data.proof !== (identity ? 'process-handle-signaled' : 'process-handle-live')
72
- || !(identity ? ['gone', 'stopped'] : ['captured']).includes(data.outcome)))
73
- return { ...base, reason: 'process-receipt-incomplete' };
74
- return { schema: OWNED_PROCESS_SCHEMA, pid, ok: data.ok, outcome: data.outcome, proof: data.proof,
75
- identity: identity ?? (validIdentity(actual) ? actual : null), nativeError: data.nativeError ?? null,
76
- ...(identity && validIdentity(actual) && (actual.birth !== identity.birth || actual.exe.toLowerCase() !== identity.exe.toLowerCase()) ? { observedIdentity: actual } : {}),
77
- ...(data.ok ? {} : { reason: data.proof }) };
78
- }
@@ -1,9 +0,0 @@
1
- // The exact-identity stop call; never infer custody from a PID alone.
2
- import { ownedProcess as call } from './owned-process.mjs';
3
- import { OWNED_PROCESS_SCHEMA, validProcessIdentity as validIdentity } from '../../lib/process-identity.mjs';
4
-
5
- /** Stop only the process object whose immutable birth and executable were captured by its owner; a signaled handle proves closure, not descendant closure. */
6
- export function stopOwnedProcess(identity, options = {}) {
7
- if (!validIdentity(identity)) return { schema: OWNED_PROCESS_SCHEMA, pid: identity?.pid ?? null, ok: false, outcome: 'refused', reason: 'process-custody-required', identity: identity ?? null };
8
- return call(identity.pid, { ...options, identity });
9
- }