@starci/hfs 4.7.32 → 4.7.34
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.
- package/CHANGELOG.md +10 -0
- package/README.md +13 -6
- package/emit/contracts.mjs +4 -3
- package/emit/form-bounds-reader.ts.partial +58 -0
- package/emit/form-bounds.mjs +155 -0
- package/emit/graphql-input-bounds.mjs +97 -0
- package/emit/input-bounds.mjs +44 -0
- package/emit/operation-schemas.mjs +137 -0
- package/emit/operations-worker.mjs +2 -2
- package/emit/operations.mjs +52 -16
- package/emit/schema-worker.mjs +17 -4
- package/emit/type-schema.mjs +17 -5
- package/package.json +9 -2
- package/release/gate.mjs +18 -23
- package/release/index.mjs +1 -1
- package/release/push.mjs +4 -5
- package/runtime/engine/admission.mjs +331 -0
- package/runtime/engine/canonical-json.mjs +14 -0
- package/runtime/engine/db/blob.mjs +380 -0
- package/runtime/engine/db/event-compact.mjs +73 -0
- package/runtime/engine/db/ledger-open.mjs +38 -0
- package/runtime/engine/db/ledger-paths.mjs +87 -0
- package/runtime/engine/db/ledger.mjs +1068 -0
- package/runtime/engine/db/machine-connection.mjs +150 -0
- package/runtime/engine/db/machine-open.mjs +60 -0
- package/runtime/engine/db/machine-schema.mjs +105 -0
- package/runtime/engine/db/machine.mjs +1317 -0
- package/runtime/engine/db/provider-reservations.mjs +127 -0
- package/runtime/engine/db/provision-authority.mjs +76 -0
- package/runtime/engine/db/provision-lock.mjs +29 -0
- package/runtime/engine/db/provision-operations.mjs +146 -0
- package/runtime/engine/db/ref-value.mjs +67 -0
- package/runtime/engine/db/schema/machine.sql +920 -0
- package/runtime/engine/db/schema/runtime.sql +1113 -0
- package/runtime/engine/refuse.mjs +11 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +65 -9
- package/runtime/knowledge/hfs/design-truths.yaml +149 -0
- package/runtime/knowledge/hfs/fe-stack.yaml +23 -5
- package/runtime/knowledge/hfs/rules.yaml +269 -48
- package/runtime/knowledge/hfs/slots.yaml +113 -53
- package/runtime/knowledge/patterns/be/api.yaml +46 -20
- package/runtime/knowledge/patterns/be/event-bus.yaml +19 -12
- package/runtime/knowledge/patterns/be/idempotency.yaml +265 -0
- package/runtime/knowledge/patterns/be/jobs.yaml +17 -12
- package/runtime/knowledge/patterns/be/projections.yaml +2 -2
- package/runtime/knowledge/patterns/be/queues.yaml +57 -22
- package/runtime/knowledge/patterns/be/reactors.yaml +1 -1
- package/runtime/knowledge/patterns/be/realtime.yaml +326 -27
- package/runtime/knowledge/patterns/be/saga.yaml +2 -2
- package/runtime/knowledge/sonar-gate.yaml +1 -1
- package/runtime/modules/kernel/command-policy.yaml +249 -0
- package/runtime/modules/kernel/failure-codes.yaml +211 -52
- package/runtime/modules/kernel/hook-input.yaml +6 -0
- package/runtime/modules/kernel/protected-zone.yaml +81 -0
- package/runtime/modules/kernel/removed-vocabulary.yaml +62 -0
- package/runtime/modules/kernel/roles.yaml +405 -0
- package/runtime/modules/models/registry.yaml +3 -1
- package/runtime/modules/models/runtimes.yaml +2 -0
- package/runtime/modules/schemas/application-stacks.schema.yaml +10 -2
- package/runtime/packages/cli/src/catalog.generated.mjs +46 -59
- package/runtime/packages/grammar/scripts/build-stamp.mjs +98 -0
- package/runtime/scripts/agent/codex-app-server.mjs +79 -0
- package/runtime/scripts/agent/codex-mirror-source.mjs +34 -0
- package/runtime/scripts/agent/host-agents.mjs +80 -0
- package/runtime/scripts/agent/launch-trust-policy.mjs +53 -0
- package/runtime/scripts/agent/trust-launch.mjs +191 -0
- package/runtime/scripts/agent/trust.mjs +523 -0
- package/runtime/scripts/api/fs/rename-over.mjs +21 -0
- package/runtime/scripts/api/fs/temp-path.mjs +8 -0
- package/runtime/scripts/api/fs/write-json-file.mjs +10 -0
- package/runtime/scripts/api/git/diff-names.mjs +8 -0
- package/runtime/scripts/api/git/head-time.mjs +10 -0
- package/runtime/scripts/api/git/index-lock.mjs +78 -0
- package/runtime/scripts/api/git/is-ancestor.mjs +5 -0
- package/runtime/scripts/api/git/log.mjs +5 -0
- package/runtime/scripts/api/git/merge-base-query.mjs +5 -0
- package/runtime/scripts/api/git/merge-tree.mjs +5 -0
- package/runtime/scripts/api/git/rev-list.mjs +5 -0
- package/runtime/scripts/api/git/rev-parse.mjs +13 -0
- package/runtime/scripts/api/git/status-query.mjs +5 -0
- package/runtime/scripts/api/git/worktree-list-query.mjs +5 -0
- package/runtime/scripts/api/node/spawn-node.mjs +6 -0
- package/runtime/scripts/api/npm/dist-tag.mjs +8 -0
- package/runtime/scripts/api/npm/lib.mjs +32 -0
- package/runtime/scripts/api/npm/pack-dry-run.mjs +9 -0
- package/runtime/scripts/api/npm/run-npm.mjs +6 -0
- package/runtime/scripts/api/npm/view.mjs +30 -0
- package/runtime/scripts/api/npm/whoami.mjs +8 -0
- package/runtime/scripts/api/process/hide-child-windows.mjs +31 -0
- package/runtime/scripts/api/process/kill-tree.mjs +22 -0
- package/runtime/scripts/api/process/lib.mjs +125 -0
- package/runtime/scripts/api/process/probe-guard-command.mjs +74 -0
- package/runtime/scripts/api/process/process-list.mjs +28 -0
- package/runtime/scripts/api/process/system-tool.mjs +37 -0
- package/runtime/scripts/gates/canon-digest.mjs +81 -0
- package/runtime/scripts/gates/canon-pins.mjs +16 -0
- package/runtime/scripts/gates/commit-proof.mjs +53 -0
- package/runtime/scripts/gates/gate-input.mjs +98 -0
- package/runtime/scripts/gates/gate.mjs +669 -0
- package/runtime/scripts/gates/grammar-dist.mjs +206 -0
- package/runtime/scripts/gates/runtime-package-selection.mjs +27 -0
- package/runtime/scripts/gates/test-world-run.mjs +254 -0
- package/runtime/scripts/gates/type-impact.mjs +277 -0
- package/runtime/scripts/gates/type-program.mjs +113 -0
- package/runtime/scripts/guards/assigned-command.mjs +12 -0
- package/runtime/scripts/guards/call-rights.mjs +47 -0
- package/runtime/scripts/guards/command-guard.mjs +468 -0
- package/runtime/scripts/guards/command-policy.mjs +342 -0
- package/runtime/scripts/guards/critic-reach.mjs +82 -0
- package/runtime/scripts/guards/deps-guard.mjs +150 -0
- package/runtime/scripts/guards/env-dump-verdict.mjs +89 -0
- package/runtime/scripts/guards/git-pathspec.mjs +225 -0
- package/runtime/scripts/guards/git-policy.mjs +355 -0
- package/runtime/scripts/guards/guards-root.mjs +27 -0
- package/runtime/scripts/guards/hook-install.mjs +343 -0
- package/runtime/scripts/guards/hook-io.mjs +76 -0
- package/runtime/scripts/guards/hook-verdict.mjs +12 -0
- package/runtime/scripts/guards/install-verdict.mjs +41 -0
- package/runtime/scripts/guards/kernel-seat.mjs +63 -0
- package/runtime/scripts/guards/launch-verdict.mjs +74 -0
- package/runtime/scripts/guards/op-starci.mjs +16 -0
- package/runtime/scripts/guards/op-write-scope.mjs +27 -0
- package/runtime/scripts/guards/orca-self-lifecycle.mjs +39 -0
- package/runtime/scripts/guards/process-kill-verdict.mjs +106 -0
- package/runtime/scripts/guards/protected-zone.mjs +56 -0
- package/runtime/scripts/guards/refusals.mjs +27 -0
- package/runtime/scripts/guards/release-definition.mjs +185 -0
- package/runtime/scripts/guards/release-push-gate.mjs +72 -0
- package/runtime/scripts/guards/release-record.mjs +58 -0
- package/runtime/scripts/guards/release-suite-mode.mjs +15 -0
- package/runtime/scripts/guards/rights.mjs +268 -0
- package/runtime/scripts/guards/seat-tools.mjs +47 -0
- package/runtime/scripts/guards/shell-commands.mjs +328 -0
- package/runtime/scripts/guards/supervisor-seat.mjs +80 -0
- package/runtime/scripts/guards/verdict-process.mjs +81 -0
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +10 -10
- package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +1 -1
- package/runtime/scripts/hfs/architecture/operation-contract-roots.mjs +53 -0
- package/runtime/scripts/hfs/architecture/reachability.mjs +1 -1
- package/runtime/scripts/hfs/architecture/sql-owner.mjs +17 -6
- package/runtime/scripts/hfs/architecture/sql-token-forms.mjs +105 -0
- package/runtime/scripts/hfs/architecture/sql-tokens.mjs +38 -52
- package/runtime/scripts/hfs/architecture/supabase-keys.mjs +261 -0
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +49 -3
- package/runtime/scripts/hfs/architecture/typescript.mjs +1 -1
- package/runtime/scripts/hfs/check.mjs +6 -4
- package/runtime/scripts/hfs/coverage-scope.mjs +28 -1
- package/runtime/scripts/hfs/db-restamp-verb.mjs +56 -16
- package/runtime/scripts/hfs/declaration-shape.mjs +4 -4
- package/runtime/scripts/hfs/declaration-slots.mjs +14 -3
- package/runtime/scripts/hfs/manifest-shape.mjs +10 -6
- package/runtime/scripts/hfs/rule-params-shape.mjs +18 -6
- package/runtime/scripts/hfs/rules/a15-findings.mjs +4 -1
- package/runtime/scripts/hfs/rules/bypass-markers.mjs +22 -7
- package/runtime/scripts/hfs/rules/database-migrations.mjs +69 -14
- package/runtime/scripts/hfs/rules/database-queue-sources.mjs +9 -7
- package/runtime/scripts/hfs/rules/database-queues.mjs +68 -18
- package/runtime/scripts/hfs/rules/edition.mjs +56 -12
- package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +1 -10
- package/runtime/scripts/hfs/rules/fe-stack.mjs +12 -7
- package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +1 -1
- package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +75 -9
- package/runtime/scripts/hfs/rules/stacks-runtime.mjs +1 -1
- package/runtime/scripts/hfs/rules/stacks-swarm.mjs +15 -4
- package/runtime/scripts/hfs/rules/text-integrity.mjs +73 -0
- package/runtime/scripts/hfs/runtime-rules/hook-shape.mjs +74 -0
- package/runtime/scripts/hfs/runtime-rules/release-notes.mjs +44 -0
- package/runtime/scripts/hfs/sealed-members.mjs +3 -3
- package/runtime/scripts/hfs/slot-side-problems.mjs +3 -3
- package/runtime/scripts/hfs/slots.mjs +2 -1
- package/runtime/scripts/hfs/stack-deploy-config.mjs +22 -5
- package/runtime/scripts/hfs/stacks-gate.mjs +28 -7
- package/runtime/scripts/hfs/view.mjs +2 -1
- package/runtime/scripts/lib/app-data.mjs +11 -0
- package/runtime/scripts/lib/bypass-markers.mjs +272 -75
- package/runtime/scripts/lib/check-format.mjs +7 -0
- package/runtime/scripts/lib/cli-arg.mjs +120 -0
- package/runtime/scripts/lib/css-rules.mjs +220 -0
- package/runtime/scripts/lib/dom-tree.mjs +4 -0
- package/runtime/scripts/lib/escape.mjs +16 -0
- package/runtime/scripts/lib/event-items.mjs +22 -0
- package/runtime/scripts/lib/failure-code-template-errors.mjs +161 -0
- package/runtime/scripts/lib/git-dir.mjs +120 -0
- package/runtime/scripts/lib/grammar-vendor-parity.mjs +377 -0
- package/runtime/scripts/lib/guard-command.mjs +24 -0
- package/runtime/scripts/lib/hook-limits.mjs +5 -0
- package/runtime/scripts/lib/interrupt-cleanup.mjs +38 -0
- package/runtime/scripts/lib/js-lex.mjs +200 -0
- package/runtime/scripts/lib/json-formats.mjs +0 -1
- package/runtime/scripts/lib/language.mjs +94 -1
- package/runtime/scripts/lib/loosening-budgets.mjs +44 -0
- package/runtime/scripts/lib/loosening-config.mjs +361 -0
- package/runtime/scripts/lib/loosening-state.mjs +223 -0
- package/runtime/scripts/lib/menu-parts.mjs +11 -0
- package/runtime/scripts/lib/normalize.mjs +25 -0
- package/runtime/scripts/lib/op-gate.mjs +15 -0
- package/runtime/scripts/lib/redact.mjs +208 -0
- package/runtime/scripts/lib/runtime-location.mjs +21 -0
- package/runtime/scripts/lib/runtime-package-inventory.mjs +21 -0
- package/runtime/scripts/lib/runtime-package-manifests.mjs +84 -0
- package/runtime/scripts/lib/runtime-publication-tags.mjs +99 -0
- package/runtime/scripts/lib/seat-env.mjs +43 -0
- package/runtime/scripts/lib/secret-patterns.mjs +58 -9
- package/runtime/scripts/lib/shell-source.mjs +4 -0
- package/runtime/scripts/lib/source-phrases.mjs +34 -0
- package/runtime/scripts/lib/sqlite.mjs +21 -0
- package/runtime/scripts/lib/swarm-stack.mjs +2 -1
- package/runtime/scripts/lib/tar-files.mjs +39 -0
- package/runtime/scripts/lib/ts-analysis.mjs +10 -0
- package/runtime/scripts/lib/worktree-exclude.mjs +35 -0
- package/runtime/scripts/lib/worktree-record.mjs +14 -0
- package/runtime/scripts/machine/artifact-hold.mjs +52 -0
- package/runtime/scripts/machine/ask-receipts.mjs +113 -0
- package/runtime/scripts/machine/evidence-store.mjs +223 -0
- package/runtime/scripts/machine/home.mjs +216 -0
- package/runtime/scripts/machine/host-lock.mjs +184 -0
- package/runtime/scripts/machine/lock-recovery.mjs +55 -0
- package/runtime/scripts/machine/roles-contract.mjs +90 -0
- package/runtime/scripts/machine/runtime-change.mjs +20 -0
- package/runtime/scripts/machine/workflow-tree.mjs +122 -0
- package/runtime/scripts/reconciler/checklist-items.mjs +7 -0
- package/runtime/scripts/reconciler/guard-row.mjs +25 -0
- package/runtime/scripts/work/asset-slot.mjs +284 -0
- package/runtime/scripts/work/brand/brand-colour.mjs +125 -0
- package/runtime/scripts/work/brand/brand-tokens.mjs +167 -0
- package/runtime/scripts/work/brand/brand-value-source.mjs +106 -0
- package/runtime/scripts/work/brand/brand.mjs +892 -0
- package/runtime/scripts/work/draw/draw-dna-anatomy.mjs +60 -0
- package/runtime/scripts/work/draw/draw-dna-findings.mjs +256 -0
- package/runtime/scripts/work/draw/draw-dna-selector.mjs +38 -0
- package/runtime/scripts/work/draw/draw-dna.mjs +483 -0
- package/runtime/scripts/work/draw/draw-layer.mjs +305 -0
- package/runtime/scripts/work/draw/draw-rationale-page.mjs +237 -0
- package/runtime/scripts/work/draw/draw-rationale-ref.mjs +14 -0
- package/runtime/scripts/work/draw/draw-rationale.mjs +353 -0
- package/runtime/scripts/work/draw/draw-source.mjs +412 -0
- package/runtime/scripts/work/draw/draw-taste.mjs +278 -0
- package/runtime/scripts/work/draw/draw-typecheck.mjs +72 -0
- package/runtime/scripts/work/draw/rationale-findings.mjs +205 -0
- package/runtime/scripts/work/draw-grammar.mjs +175 -0
- package/runtime/scripts/work/draw-render-page.mjs +188 -0
- package/runtime/scripts/work/draw-render.mjs +626 -0
- package/runtime/scripts/work/grammar-proposal.mjs +279 -0
- package/runtime/scripts/work/png.mjs +237 -0
- package/runtime/scripts/work/render-tools.mjs +64 -0
- package/runtime/scripts/work/work-io.mjs +119 -0
- package/scaffold/add-inbox-lite.mjs +2 -2
- package/scaffold/add-plan.mjs +40 -13
- package/scaffold/add-queue-lite.mjs +372 -0
- package/scaffold/add.mjs +5 -2
- package/scaffold/app.mjs +1 -2
- package/scaffold/edition-gate.mjs +11 -7
- package/scaffold/lite-exports.mjs +10 -0
- package/scaffold/pattern-file-slots.mjs +33 -0
- package/scaffold/test-world.mjs +1 -1
- package/scaffold/yaml-block-edit.mjs +117 -0
- package/src/main.mjs +5 -4
- package/stack/deploy.mjs +7 -3
- package/stack/post-deploy.mjs +48 -0
- package/sync/index.mjs +10 -0
- package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +1 -1
- package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +1 -1
- package/templates/app/codegen-graphql.mjs.partial +59 -12
- package/templates/app/hooks/husky/pre-commit +2 -2
- package/templates/app/hooks/husky/pre-push +10 -3
- package/templates/app/package-scripts/redis-dev.json +3 -0
- package/templates/app/package-scripts-lite/package.json +1 -0
- package/templates/app/skeleton/.starcistacks/application-stacks.yaml +36 -8
- package/templates/app/skeleton/.starcistacks/dev/README.md +1 -1
- package/templates/app/skeleton/.starcistacks/production/README.md +10 -9
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/postgres-init.sh +21 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +1 -1
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +1 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +47 -5
- package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +5 -3
- package/templates/app/skeleton/README.md +2 -0
- package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +5 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/README.md +2 -2
- package/templates/app/skeleton-lite/.starcistacks/production/README.md +1 -1
- package/templates/app/skeleton-lite/README.md +1 -1
- package/templates/be/inbox/platform/client-operation-outcomes.sql.tpl +18 -0
- package/templates/be/inbox/platform/event-bus/errors/event-bus.error.ts.tpl +16 -0
- package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +10 -0
- package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +1 -1
- package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +15 -3
- package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +11 -3
- package/templates/be/inbox/platform/event-bus/index.ts.tpl +2 -0
- package/templates/be/inbox/platform/event-bus/messages/event-bus.messages.ts.tpl +7 -0
- package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +2 -2
- package/templates/be/inbox/platform/idempotency/errors/idempotency.error.ts.tpl +19 -0
- package/templates/be/inbox/platform/idempotency/index.ts.tpl +2 -0
- package/templates/be/inbox/platform/idempotency/messages/idempotency.messages.ts.tpl +13 -0
- package/templates/be/inbox/platform/inbox/client-operation.policy.spec.ts.tpl +32 -0
- package/templates/be/inbox/platform/inbox/client-operation.policy.ts.tpl +17 -0
- package/templates/be/inbox/platform/inbox/errors/inbox.error.ts.tpl +22 -0
- package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +21 -0
- package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +334 -11
- package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +55 -4
- package/templates/be/inbox/platform/inbox/index.ts.tpl +2 -0
- package/templates/be/inbox/platform/inbox/messages/inbox.messages.ts.tpl +15 -0
- package/templates/be/inbox/platform/inbox/persistence/inbox.rows.ts.tpl +33 -0
- package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +26 -0
- package/templates/be/inbox/platform/inbox/request-fingerprint.policy.spec.ts.tpl +76 -0
- package/templates/be/inbox/platform/inbox/request-fingerprint.policy.ts.tpl +36 -0
- package/templates/be/inbox/platform/migration.sql.tpl +1 -0
- package/templates/be/inbox/service.spec.ts.tpl +4 -4
- package/templates/be/inbox/service.ts.tpl +3 -1
- package/templates/be/inbox/webhook-door.spec.ts.tpl +3 -2
- package/templates/be/inbox/webhook-door.ts.tpl +3 -2
- package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +3 -0
- package/templates/be/patterns/event-bus/platform/event-bus.contracts.ts.tpl +10 -0
- package/templates/be/patterns/event-bus/platform/event-bus.port.ts.tpl +2 -2
- package/templates/be/patterns/event-bus/platform/event-bus.service.spec.ts.tpl +16 -3
- package/templates/be/patterns/event-bus/platform/event-bus.service.ts.tpl +11 -3
- package/templates/be/patterns/event-bus/platform/event-envelope.policy.spec.ts.tpl +139 -50
- package/templates/be/patterns/event-bus/platform/event-envelope.policy.ts.tpl +25 -2
- package/templates/be/patterns/event-bus/platform/event-relay.service.spec.ts.tpl +139 -78
- package/templates/be/patterns/event-bus/platform/event-relay.service.ts.tpl +10 -6
- package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +262 -159
- package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +32 -5
- package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +2 -0
- package/templates/be/patterns/event-bus/platform/persistence/entities/event-outbox.entity.ts.tpl +5 -1
- package/templates/be/patterns/event-bus/platform/persistence/event-bus.rows.ts.tpl +2 -0
- package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +4 -4
- package/templates/be/patterns/event-bus/platform/persistence/migrations/1789800009000-create-event-outbox.ts.tpl +1 -0
- package/templates/be/patterns/idempotency/platform/idempotency/errors/idempotency.error.ts.tpl +19 -0
- package/templates/be/patterns/idempotency/platform/idempotency/index.ts.tpl +2 -0
- package/templates/be/patterns/idempotency/platform/idempotency/messages/idempotency.messages.ts.tpl +13 -0
- package/templates/be/patterns/idempotency/platform/inbox/client-operation.policy.spec.ts.tpl +32 -0
- package/templates/be/patterns/idempotency/platform/inbox/client-operation.policy.ts.tpl +17 -0
- package/templates/be/patterns/idempotency/platform/inbox/client-outcome-backfill.client.spec.ts.tpl +66 -0
- package/templates/be/patterns/idempotency/platform/inbox/client-outcome-backfill.client.ts.tpl +24 -0
- package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-backfill.client.spec.ts.tpl +60 -0
- package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-backfill.client.ts.tpl +12 -0
- package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-release.client.spec.ts.tpl +49 -0
- package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-release.client.ts.tpl +12 -0
- package/templates/be/patterns/idempotency/platform/inbox/effectful.decorators.ts.tpl +7 -0
- package/templates/be/patterns/idempotency/platform/inbox/errors/inbox.error.ts.tpl +22 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.decorators.ts.tpl +17 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +7 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.module.ts.tpl +28 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.options.ts.tpl +7 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.port.ts.tpl +44 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.service.spec.ts.tpl +388 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.service.ts.tpl +77 -0
- package/templates/be/patterns/idempotency/platform/inbox/index.ts.tpl +10 -0
- package/templates/be/patterns/idempotency/platform/inbox/messages/inbox.messages.ts.tpl +15 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/connection.ts.tpl +9 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/entities/inbox-claim.entity.ts.tpl +25 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.rows.ts.tpl +33 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.sql.ts.tpl +39 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/migrations/1789800005000-create-inbox-claims.ts.tpl +21 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/migrations/1792800000000-client-operation-outcomes.ts.tpl +21 -0
- package/templates/be/patterns/idempotency/platform/inbox/request-fingerprint.policy.spec.ts.tpl +76 -0
- package/templates/be/patterns/idempotency/platform/inbox/request-fingerprint.policy.ts.tpl +36 -0
- package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +1 -0
- package/templates/be/patterns/jobs/platform/job-claim.service.spec.ts.tpl +4 -3
- package/templates/be/patterns/jobs/platform/job-claim.service.ts.tpl +2 -0
- package/templates/be/patterns/jobs/platform/job-runner.service.spec.ts.tpl +20 -2
- package/templates/be/patterns/jobs/platform/job-runner.service.ts.tpl +1 -0
- package/templates/be/patterns/jobs/platform/jobs.contracts.ts.tpl +4 -0
- package/templates/be/patterns/jobs/platform-lite/jobs.index.ts.tpl +10 -0
- package/templates/be/patterns/jobs/platform-lite/jobs.sql.tpl +44 -0
- package/templates/be/patterns/jobs/platform-lite/persistence.jobs.sql.ts.tpl +33 -0
- package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.spec.ts.tpl +203 -18
- package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.ts.tpl +78 -22
- package/templates/be/patterns/queues/platform/errors/queue.error.ts.tpl +19 -0
- package/templates/be/patterns/queues/platform/messages/queue.messages.ts.tpl +13 -0
- package/templates/be/patterns/queues/platform/persistence.migration.ts.tpl +1 -0
- package/templates/be/patterns/queues/platform/persistence.queue-outbox.entity.ts.tpl +4 -0
- package/templates/be/patterns/queues/platform/persistence.queue.rows.ts.tpl +2 -0
- package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +4 -4
- package/templates/be/patterns/queues/platform/queue-outbox.service.spec.ts.tpl +14 -2
- package/templates/be/patterns/queues/platform/queue-outbox.service.ts.tpl +5 -2
- package/templates/be/patterns/queues/platform/queue-relay.service.spec.ts.tpl +72 -74
- package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +1 -1
- package/templates/be/patterns/queues/platform/queue-transport.port.ts.tpl +23 -8
- package/templates/be/patterns/queues/platform/queue-worker.service.spec.ts.tpl +29 -8
- package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +7 -3
- package/templates/be/patterns/queues/platform/queue.contracts.ts.tpl +5 -0
- package/templates/be/patterns/queues/platform/queue.index.ts.tpl +2 -0
- package/templates/be/patterns/queues/platform/queue.log-events.ts.tpl +4 -0
- package/templates/be/patterns/queues/platform/queue.module.ts.tpl +2 -1
- package/templates/be/patterns/queues/platform/queue.port.ts.tpl +1 -1
- package/templates/be/patterns/queues/platform-lite/bullmq-queue-transport.client.spec.ts.tpl +417 -0
- package/templates/be/patterns/queues/platform-lite/bullmq-queue-transport.client.ts.tpl +172 -0
- package/templates/be/patterns/queues/platform-lite/persistence.queue.sql.ts.tpl +12 -0
- package/templates/be/patterns/queues/platform-lite/queue-outbox.sql.tpl +32 -0
- package/templates/be/patterns/queues/platform-lite/queue-relay.service.spec.ts.tpl +148 -0
- package/templates/be/patterns/queues/platform-lite/queue-relay.service.ts.tpl +51 -0
- package/templates/be/patterns/queues/platform-lite/queue-transport.port.ts.tpl +163 -0
- package/templates/be/patterns/queues/platform-lite/queue.config.ts.tpl +17 -0
- package/templates/be/patterns/queues/platform-lite/queue.error.ts.tpl +25 -0
- package/templates/be/patterns/queues/platform-lite/queue.index.ts.tpl +8 -0
- package/templates/be/patterns/queues/platform-lite/queue.messages.ts.tpl +21 -0
- package/templates/be/patterns/queues/platform-lite/queue.module.ts.tpl +74 -0
- package/templates/be/patterns/queues/platform-lite/queue.options.ts.tpl +38 -0
- package/templates/be/patterns/queues/queue.queue.ts.tpl +2 -2
- package/templates/be/patterns/reactors/application.contracts.ts.tpl +2 -0
- package/templates/be/patterns/reactors/consumer.ts.tpl +5 -1
- package/templates/be/platform/graphql/graphql.contracts.ts +2 -1
- package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +1 -1
- package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +6 -1
- package/templates/be/skeleton/apps/__app__/src/main.ts +7 -4
- package/templates/be/skeleton/apps/cli/src/compose/platform.ts +4 -0
- package/templates/be/skeleton/apps/cli/src/main.ts +7 -4
- package/templates/be/skeleton/src/modules/platform/database/index.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/http/errors/http.error.ts +22 -0
- package/templates/be/skeleton/src/modules/platform/http/fetch-http-client.client.spec.ts +235 -0
- package/templates/be/skeleton/src/modules/platform/http/fetch-http-client.client.ts +85 -0
- package/templates/be/skeleton/src/modules/platform/http/graphql-call.mapper.spec.ts +92 -0
- package/templates/be/skeleton/src/modules/platform/http/graphql-call.mapper.ts +52 -0
- package/templates/be/skeleton/src/modules/platform/http/http.decorators.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/http/http.module.ts +19 -0
- package/templates/be/skeleton/src/modules/platform/http/http.options.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/http/http.port.ts +40 -0
- package/templates/be/skeleton/src/modules/platform/http/index.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/http/messages/http.messages.ts +15 -0
- package/templates/be/skeleton/src/modules/platform/ids/ids.decorators.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/ids/ids.module.ts +19 -0
- package/templates/be/skeleton/src/modules/platform/ids/ids.options.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/ids/ids.port.ts +5 -0
- package/templates/be/skeleton/src/modules/platform/ids/index.ts +4 -0
- package/templates/be/skeleton/src/modules/platform/ids/system-ids.service.spec.ts +15 -0
- package/templates/be/skeleton/src/modules/platform/ids/system-ids.service.ts +10 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation-request.policy.spec.ts +59 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation-request.policy.ts +21 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.contracts.ts +16 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.policy.spec.ts +150 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.policy.ts +42 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.port.ts +15 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.service.spec.ts +23 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.service.ts +28 -0
- package/templates/be/skeleton/src/modules/platform/logging/index.ts +14 -1
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +39 -6
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +15 -3
- package/templates/be/skeleton/src/modules/platform/logging/logging.decorators.ts +17 -2
- package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +13 -1
- package/templates/be/skeleton/src/modules/platform/logging/logging.module.ts +13 -3
- package/templates/be/skeleton/src/modules/platform/logging/nest-logger.service.spec.ts +69 -0
- package/templates/be/skeleton/src/modules/platform/logging/nest-logger.service.ts +48 -0
- package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
- package/templates/be/skeleton/src/modules/platform/primitives/record.policy.spec.ts +12 -0
- package/templates/be/skeleton/src/modules/platform/primitives/record.policy.ts +3 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +6 -1
- package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +4 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
- package/templates/be/skeleton-lite/src/modules/platform/logging/index.ts +14 -1
- package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +12 -0
- package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
- package/templates/be/test-world/world/test-apps.options.ts.tpl +2 -0
- package/templates/be/test-world/world/test-world.config.ts.tpl +1 -0
- package/upgrade/check.mjs +12 -135
- package/upgrade/file-state.mjs +1 -16
- package/upgrade/index.mjs +34 -361
- package/runtime/scripts/lib/check-code-loosening.mjs +0 -128
- package/runtime/scripts/lib/gate-loosening.mjs +0 -292
- package/templates/app/upgrade-full/.starcistacks/application-stacks.yaml +0 -89
- package/templates/app/upgrade-full/.starcistacks/dev/README.md +0 -16
- package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/api.yaml +0 -13
- package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/compose.yaml +0 -3
- package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/postgres.yaml +0 -12
- package/templates/app/upgrade-full/.starcistacks/dev/infra/metadata.json +0 -7
- package/templates/be/skeleton-lite/apps/api/src/main.ts +0 -26
- package/templates/be/upgrade-full/apps/cli/src/app.module.ts +0 -12
- package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +0 -7
- package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +0 -5
- package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +0 -9
- package/templates/be/upgrade-full/apps/cli/src/main.ts +0 -29
- package/templates/be/upgrade-full/src/features/cli/cli.module.ts +0 -7
- package/templates/be/upgrade-full/src/features/cli/migrate/subs/run.cli.ts +0 -23
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// index-lock.mjs — the ONE recovery of a stale `.git/index.lock` in a shared product checkout.
|
|
2
|
+
//
|
|
3
|
+
// Every workflow of a product repo works in one checkout (one shared checkout per product). A git process that
|
|
4
|
+
// dies mid-write (a host terminal wipe, a killed worker) leaves `<gitdir>/index.lock` behind, and from then on
|
|
5
|
+
// every `git add` / `git commit` in that checkout refuses "Unable to create .../index.lock: File exists" - for
|
|
6
|
+
// every workflow at once. One op once settled blocked on
|
|
7
|
+
// a 403 KB lock left over with no git process alive; the worker rightly would not delete a shared lock.
|
|
8
|
+
//
|
|
9
|
+
// indexLock removes such a lock only when all of these hold:
|
|
10
|
+
// - it is a plain file (never a link), older than allocation.housekeeping.gitIndexLockStaleMs (runtimes.yaml);
|
|
11
|
+
// - the host process table was read (a failed probe removes nothing), and no git process may be working on
|
|
12
|
+
// this repository: a git-family process whose command line names this repo (-C, --git-dir, --work-tree) or
|
|
13
|
+
// names no repository at all (its cwd cannot be read, so it may be this repo's) keeps the lock;
|
|
14
|
+
// - the lock is the same file (mtime and size) after the probe as before it.
|
|
15
|
+
// The caller reads the process table (`list`) and records a removal (`record`): scripts/machine/lock-recovery.mjs composes
|
|
16
|
+
// both, as a `git-index-lock-removed` Supervisor audit event (machine.sqlite sup_events); every other outcome is returned,
|
|
17
|
+
// never thrown. Callers: the command guard before an op worker's git command runs (scripts/guards/command-guard.mjs,
|
|
18
|
+
// through lock-recovery.mjs preflightIndexLock) and the housekeeping area `gitlocks` (scripts/housekeeping/hk-git-locks.mjs).
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import { gitDirOf, lockStat, processOnRepo } from '../../lib/git-dir.mjs';
|
|
22
|
+
|
|
23
|
+
// A lock younger than `staleMs` (or one with no usable threshold) is not stale yet.
|
|
24
|
+
const notStale = (ageMs, staleMs) => {
|
|
25
|
+
const threshold = Number(staleMs);
|
|
26
|
+
return Number.isNaN(threshold) || threshold <= 0 || ageMs < threshold;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const initialLockState = (repo, staleMs, now) => {
|
|
30
|
+
const gitDir = gitDirOf(repo);
|
|
31
|
+
const lock = gitDir ? path.join(gitDir, 'index.lock') : null;
|
|
32
|
+
const out = { repo: path.resolve(String(repo ?? '')), lock, state: 'absent' };
|
|
33
|
+
const before = lock ? lockStat(lock) : null;
|
|
34
|
+
if (!before) return { out, before, result: out };
|
|
35
|
+
out.ageMs = Math.max(0, now - before.mtimeMs);
|
|
36
|
+
out.bytes = before.size;
|
|
37
|
+
if (before.isSymbolicLink() || !before.isFile()) return { out, before, result: { ...out, state: 'not-a-file' } };
|
|
38
|
+
if (notStale(out.ageMs, staleMs)) return { out, before, result: { ...out, state: 'fresh' } };
|
|
39
|
+
return { out, before, result: null };
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const processHoldersOnRepo = (list, repo) => {
|
|
43
|
+
let procs = null;
|
|
44
|
+
try { procs = typeof list === 'function' ? list() : null; } catch { procs = null; }
|
|
45
|
+
return Array.isArray(procs) ? procs.filter((p) => processOnRepo(p, repo) !== 'other') : null;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const removeVerifiedLock = ({ lock, out, before, apply, record }) => {
|
|
49
|
+
const after = lockStat(lock);
|
|
50
|
+
if (!after) return { ...out, state: 'absent' };
|
|
51
|
+
if (after.mtimeMs !== before.mtimeMs || after.size !== before.size) return { ...out, state: 'changed' };
|
|
52
|
+
if (!apply) return { ...out, state: 'would-remove' };
|
|
53
|
+
try { fs.unlinkSync(lock); } catch (error) {
|
|
54
|
+
if (error?.code !== 'ENOENT') return { ...out, state: 'error', error: `${error?.code ?? 'ERROR'}: ${error?.message ?? error}` };
|
|
55
|
+
}
|
|
56
|
+
const removed = { ...out, state: 'removed', mtime: new Date(before.mtimeMs).toISOString() };
|
|
57
|
+
if (typeof record === 'function') { try { record(removed); } catch { /* the removal stands without its event */ } }
|
|
58
|
+
return removed;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Recover `repo`'s stale index lock. `staleMs` is the age a lock must pass (allocation.housekeeping.
|
|
63
|
+
* gitIndexLockStaleMs); `apply` false reports `would-remove` and removes nothing; `list()` is the caller's git-process
|
|
64
|
+
* probe ([{pid, name, commandLine}] or null; none given: probe-failed, nothing removed); `record(result)` is called once
|
|
65
|
+
* for a removal.
|
|
66
|
+
* Returns {repo, lock, state, ageMs?, holders?, error?}; state is one of absent | fresh | not-a-file | probe-failed |
|
|
67
|
+
* held | changed | would-remove | removed | error.
|
|
68
|
+
*/
|
|
69
|
+
export function indexLock({ repo, staleMs, now = Date.now(), apply = true, list = null, record = null } = {}) {
|
|
70
|
+
const initial = initialLockState(repo, staleMs, now);
|
|
71
|
+
if (initial.result) return initial.result;
|
|
72
|
+
const { out, before } = initial;
|
|
73
|
+
const { lock } = out;
|
|
74
|
+
const holders = processHoldersOnRepo(list, out.repo);
|
|
75
|
+
if (holders === null) return { ...out, state: 'probe-failed' };
|
|
76
|
+
if (holders.length) return { ...out, state: 'held', holders: holders.map((p) => ({ pid: p.pid, name: p.name, commandLine: String(p.commandLine ?? '').slice(0, 200) })) };
|
|
77
|
+
return removeVerifiedLock({ lock, out, before, apply, record });
|
|
78
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// is-ancestor.mjs — `git merge-base --is-ancestor <a> <b>`: whether commit `a` is in the history of `b`.
|
|
2
|
+
import { gitRunner } from './lib.mjs';
|
|
3
|
+
|
|
4
|
+
/** True when `a` is an ancestor of (or equal to) `b`; false otherwise, and when a ref does not resolve. git: the caller's runner or null. */
|
|
5
|
+
export const isAncestor = (cwd, a, b, { git = null } = {}) => gitRunner(git)(['merge-base', '--is-ancestor', a, b], { cwd }).ok;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// log.mjs — `git log <args>`: the commit history in a caller-chosen format.
|
|
2
|
+
import { runGit } from './lib.mjs';
|
|
3
|
+
|
|
4
|
+
/** The spawn result {status, stdout, stderr, error} of `git log ...args`; options are runGit's (cwd or dir, config, timeout, maxBuffer, input, encoding, env, git). */
|
|
5
|
+
export const log = (args = [], options = {}) => runGit(['log', ...args], options);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// merge-base-query.mjs — `git merge-base <args>`: one merge-base query (the best common ancestor, or --is-ancestor's exit status).
|
|
2
|
+
import { runGit } from './lib.mjs';
|
|
3
|
+
|
|
4
|
+
/** The spawn result {status, stdout, stderr, error} of `git merge-base ...args`; options are runGit's (cwd or dir, config, timeout, maxBuffer, input, encoding, env, git). */
|
|
5
|
+
export const mergeBaseQuery = (args = [], options = {}) => runGit(['merge-base', ...args], options);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// merge-tree.mjs — `git merge-tree <args>`: a merge computed without touching the index or the working tree.
|
|
2
|
+
import { runGit } from './lib.mjs';
|
|
3
|
+
|
|
4
|
+
/** The spawn result {status, stdout, stderr, error} of `git merge-tree ...args`; options are runGit's (cwd or dir, config, timeout, maxBuffer, input, encoding, env, git). */
|
|
5
|
+
export const mergeTree = (args = [], options = {}) => runGit(['merge-tree', ...args], options);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// rev-list.mjs — `git rev-list <args>`: the commits reachable from one set of refs and not another.
|
|
2
|
+
import { runGit } from './lib.mjs';
|
|
3
|
+
|
|
4
|
+
/** The spawn result {status, stdout, stderr, error} of `git rev-list ...args`; options are runGit's (cwd or dir, config, timeout, maxBuffer, input, encoding, env, git). */
|
|
5
|
+
export const revList = (args = [], options = {}) => runGit(['rev-list', ...args], options);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// rev-parse.mjs — the commit-resolution read used by runtime Git consumers.
|
|
2
|
+
import { gitRunner } from './lib.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolve a Git revision to a commit in `cwd`, peeling tags with `^{commit}`.
|
|
6
|
+
* Returns the trimmed commit ID only when Git succeeds with output; unresolved
|
|
7
|
+
* or non-commit revisions, failed reads and empty output return null.
|
|
8
|
+
* `git` may supply a caller-owned runner instead of the native read.
|
|
9
|
+
*/
|
|
10
|
+
export const revParse = (cwd, ref, { git = null } = {}) => {
|
|
11
|
+
const r = gitRunner(git)(['rev-parse', '--verify', '--quiet', `${ref}^{commit}`], { cwd });
|
|
12
|
+
return r.ok && r.stdout ? r.stdout : null;
|
|
13
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// status-query.mjs — `git status <args>`: what a checkout changed (--porcelain, untracked-files mode, pathspecs).
|
|
2
|
+
import { runGit } from './lib.mjs';
|
|
3
|
+
|
|
4
|
+
/** The spawn result {status, stdout, stderr, error} of `git status ...args`; options are runGit's (cwd or dir, config, timeout, maxBuffer, input, encoding, env, git). */
|
|
5
|
+
export const statusQuery = (args = [], options = {}) => runGit(['status', ...args], options);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// worktree-list-query.mjs — `git worktree list <args>`: the registered worktrees of a repository (--porcelain for records).
|
|
2
|
+
import { runGit } from './lib.mjs';
|
|
3
|
+
|
|
4
|
+
/** The spawn result {status, stdout, stderr, error} of `git worktree list ...args`; options are runGit's (cwd or dir, config, timeout, maxBuffer, input, encoding, env, git). */
|
|
5
|
+
export const worktreeListQuery = (args = [], options = {}) => runGit(['worktree', 'list', ...args], options);
|
|
@@ -0,0 +1,6 @@
|
|
|
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);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The release owner's npm dist-tag mutation edge; reads remain owned by view.mjs.
|
|
2
|
+
import { npmSpawn } from './lib.mjs';
|
|
3
|
+
|
|
4
|
+
/** Set one declared tag on an already confirmed package version; report incomplete effects as failure. */
|
|
5
|
+
export function distTag(name, version, tag, { cwd, run = npmSpawn } = {}) {
|
|
6
|
+
const result = run(['dist-tag', 'add', `${name}@${version}`, tag], { cwd, timeout: 120_000 });
|
|
7
|
+
return { ok: !result.error && !result.signal && result.status === 0, status: result.status ?? null };
|
|
8
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// scripts/api/npm/lib.mjs — the runner of npm and npx: the npm-cli.js / npx-cli.js that ship with this node install, run by
|
|
2
|
+
// that node, or the npm / npx executable beside the node binary (no .cmd shim, no shell, no PATH lookup). Utf8 text, a hidden window. The call
|
|
3
|
+
// files beside it (ci.mjs, pack-dry-run.mjs, run-npm.mjs, run-npx.mjs) each name one use; nothing outside scripts/api/npm
|
|
4
|
+
// imports this runner.
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import { withTempEnv } from '../../../engine/temp-root.mjs';
|
|
9
|
+
import { withoutTestRunner } from '../../lib/env.mjs';
|
|
10
|
+
|
|
11
|
+
/** The `<tool>-cli.js` (npm or npx) of this node install (Windows: beside node; elsewhere: <prefix>/lib/node_modules) run by node, else the `<tool>` executable beside the node binary: an absolute spawn target fixed by the node install, never a PATH lookup; null when the node has neither. */
|
|
12
|
+
const bundledCli = (tool = 'npm', platform = process.platform, execPath = process.execPath, exists = fs.existsSync) => {
|
|
13
|
+
const dir = path.dirname(execPath);
|
|
14
|
+
const cli = path.join(dir, ...(platform === 'win32' ? [] : ['..', 'lib']), 'node_modules', 'npm', 'bin', `${tool}-cli.js`);
|
|
15
|
+
if (exists(cli)) return { file: execPath, args: [cli] };
|
|
16
|
+
const sibling = path.join(dir, tool);
|
|
17
|
+
return platform !== 'win32' && exists(sibling) ? { file: sibling, args: [] } : null;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const toolSpawn = (tool, args, options) => {
|
|
21
|
+
// npm runs a package's test script, which is node --test: the child never carries an enclosing spec run's mark (scripts/lib/env.mjs withoutTestRunner).
|
|
22
|
+
const spawn = withTempEnv({ encoding: 'utf8', windowsHide: true, maxBuffer: 64 * 1024 * 1024, ...options, env: withoutTestRunner(options.env ?? process.env) });
|
|
23
|
+
const run = bundledCli(tool);
|
|
24
|
+
return run ? spawnSync(run.file, [...run.args, ...args], spawn)
|
|
25
|
+
: { status: null, stdout: '', stderr: '', signal: null, error: Object.assign(new Error(`${tool} was not found beside this node binary`), { code: 'ENOENT' }) };
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** `npm <args>`; options pass through last (cwd, timeout, env, maxBuffer, stdio). */
|
|
29
|
+
export const npmSpawn = (args, options = {}) => toolSpawn('npm', args, options);
|
|
30
|
+
|
|
31
|
+
/** `npx <args>`; options pass through last (cwd, timeout, env, maxBuffer, stdio). */
|
|
32
|
+
export const npxSpawn = (args, options = {}) => toolSpawn('npx', args, options);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// pack-dry-run.mjs — `npm pack --dry-run --json --ignore-scripts` in a package directory: the file list npm would
|
|
2
|
+
// publish (scripts/gates/canon-digest.mjs packedFiles shapes it).
|
|
3
|
+
import { npmSpawn } from './lib.mjs';
|
|
4
|
+
|
|
5
|
+
/** {status, stdout, stderr, error} of the dry run. npm runs through the runner's absolute node + npm-cli.js target: no shell, no PATH lookup. */
|
|
6
|
+
export function packDryRun(directory, { spawn = npmSpawn } = {}) {
|
|
7
|
+
const r = spawn(['pack', '--dry-run', '--json', '--ignore-scripts'], { cwd: directory });
|
|
8
|
+
return { status: r.status, stdout: r.stdout, stderr: r.stderr, error: r.error ?? null };
|
|
9
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// run-npm.mjs — `npm <args>` waited for: a package script (`run <script>`, `test`) or an install (`ci`, `install`) whose
|
|
2
|
+
// status and output the caller reads. Returns the spawnSync result {status, stdout, stderr, error, signal} (utf8 text).
|
|
3
|
+
import { npmSpawn } from './lib.mjs';
|
|
4
|
+
|
|
5
|
+
/** Runs `npm <args>`; options (cwd, env, timeout, maxBuffer, stdio) pass through. */
|
|
6
|
+
export const runNpm = (args, options = {}) => npmSpawn(args, options);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// view.mjs - `npm view`: what the npm registry says about one package version. The release gate reads it to know whether a
|
|
2
|
+
// version is published and which integrity the registry holds; it never writes.
|
|
3
|
+
import { npmSpawn } from './lib.mjs';
|
|
4
|
+
|
|
5
|
+
/** The JSON `npm view <spec> <field> --json` printed, or null when it printed none. */
|
|
6
|
+
const parsed = (text) => { try { return JSON.parse(String(text ?? '').trim() || 'null'); } catch { return null; } };
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* {state: 'present' | 'absent' | 'unreachable', integrity?, latest?, versions?, distTags?, detail?} of `name@version`. A package the registry does not
|
|
10
|
+
* know (E404) or a version it does not list is `absent` (with the registry's latest when it has one); any other failure
|
|
11
|
+
* (network, auth, a broken npm) is `unreachable`, never a guess. `run` is the spawn seam of a spec.
|
|
12
|
+
*/
|
|
13
|
+
export function view(name, version, { cwd, run = npmSpawn } = {}) {
|
|
14
|
+
const listed = run(['view', name, 'versions', 'dist-tags', '--json'], { cwd, timeout: 120_000 });
|
|
15
|
+
const err = `${listed.stderr ?? ''}${listed.error?.message ?? ''}`;
|
|
16
|
+
if (listed.error || listed.signal || listed.status === null) return { state: 'unreachable', detail: err.trim().slice(0, 200) || 'npm did not run' };
|
|
17
|
+
if (listed.status !== 0) {
|
|
18
|
+
return /\bE404\b|404 Not Found|code E404/.test(err) ? { state: 'absent', latest: '-', versions: [], distTags: {} } : { state: 'unreachable', detail: err.trim().split(/\r?\n/)[0].slice(0, 200) };
|
|
19
|
+
}
|
|
20
|
+
const doc = parsed(listed.stdout);
|
|
21
|
+
const versions = [doc?.versions].flat();
|
|
22
|
+
const distTags = doc?.['dist-tags'];
|
|
23
|
+
if (!versions.length || versions.some((entry) => typeof entry !== 'string') || !distTags || Array.isArray(distTags) || typeof distTags !== 'object' || Object.values(distTags).some((entry) => typeof entry !== 'string'))
|
|
24
|
+
return { state: 'unreachable', detail: 'the registry returned an unreadable version or dist-tag inventory' };
|
|
25
|
+
const inventory = { versions, distTags, latest: distTags.latest ?? '-' };
|
|
26
|
+
if (!versions.includes(version)) return { state: 'absent', ...inventory };
|
|
27
|
+
const dist = run(['view', `${name}@${version}`, 'dist.integrity', '--json'], { cwd, timeout: 120_000 });
|
|
28
|
+
const integrity = !dist.error && !dist.signal && dist.status === 0 ? parsed(dist.stdout) : null;
|
|
29
|
+
return typeof integrity === 'string' ? { state: 'present', integrity, ...inventory } : { state: 'unreachable', detail: 'the registry lists the version but gave no integrity' };
|
|
30
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// whoami.mjs - `npm whoami`: the npm account logged in on this host (release-publish.mjs refuses a publish from another one).
|
|
2
|
+
import { npmSpawn } from './lib.mjs';
|
|
3
|
+
|
|
4
|
+
/** The npm account logged in on this host, or null. `run` is the spawn seam of a spec. */
|
|
5
|
+
export function whoami({ cwd, run = npmSpawn } = {}) {
|
|
6
|
+
const r = run(['whoami'], { cwd, timeout: 60_000 });
|
|
7
|
+
return !r.error && !r.signal && r.status === 0 ? String(r.stdout ?? '').trim() || null : null;
|
|
8
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// hide-child-windows.mjs — the child-process defaults for background Windows entry points.
|
|
2
|
+
import cp from 'node:child_process';
|
|
3
|
+
import { syncBuiltinESMExports } from 'node:module';
|
|
4
|
+
import { withWindowsHide } from './lib.mjs';
|
|
5
|
+
|
|
6
|
+
const MARK = Symbol.for('starci.hideChildWindows');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Install process-wide hidden-window defaults for Windows child-process calls.
|
|
10
|
+
* Explicit `windowsHide` options survive, and builtin ESM exports are synced
|
|
11
|
+
* so their named imports use the wrappers. Importing this module installs the
|
|
12
|
+
* patch; detached entry points load it before spawning to avoid visible consoles.
|
|
13
|
+
* Returns true on the first Windows installation, false elsewhere or once patched.
|
|
14
|
+
*/
|
|
15
|
+
export function hideChildWindows({ platform = process.platform } = {}) {
|
|
16
|
+
if (platform !== 'win32' || cp[MARK]) return false;
|
|
17
|
+
for (const [name, hasArgv] of [['spawn', true], ['spawnSync', true], ['execFile', true], ['execFileSync', true], ['exec', false], ['execSync', false]]) {
|
|
18
|
+
const original = cp[name];
|
|
19
|
+
const wrapped = function (...list) { return original.apply(this, withWindowsHide(list, hasArgv)); };
|
|
20
|
+
for (const key of Reflect.ownKeys(original)) {
|
|
21
|
+
if (key === 'length' || key === 'name' || key === 'prototype') continue;
|
|
22
|
+
try { Object.defineProperty(wrapped, key, Object.getOwnPropertyDescriptor(original, key)); } catch { /* non-configurable */ }
|
|
23
|
+
}
|
|
24
|
+
cp[name] = wrapped;
|
|
25
|
+
}
|
|
26
|
+
cp[MARK] = true;
|
|
27
|
+
syncBuiltinESMExports();
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
hideChildWindows();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// kill-tree.mjs — the host API for a forced Windows process-tree stop.
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
|
|
4
|
+
const KILL_TREE_COMMAND = 'taskkill.exe';
|
|
5
|
+
const killTreeArgs = (pid) => ['/F', '/T', '/PID', String(pid)];
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Force-stop a caller-owned PID and its descendants with Windows taskkill.
|
|
9
|
+
* The caller proves ownership and validates the PID; this API checks neither.
|
|
10
|
+
* Non-Windows calls stop nothing and return `{ok:false,status:null,output}`.
|
|
11
|
+
* Returns `{ok,status,output}` with bounded diagnostic text; spawn failures become
|
|
12
|
+
* failure results. `ok` reflects taskkill's exit status without rechecking liveness.
|
|
13
|
+
*/
|
|
14
|
+
export function killTree(pid, { timeoutMs = 60_000, run = spawnSync, platform = process.platform } = {}) {
|
|
15
|
+
if (platform !== 'win32') return { ok: false, status: null, output: 'not windows' };
|
|
16
|
+
try {
|
|
17
|
+
const r = run(KILL_TREE_COMMAND, killTreeArgs(pid), { encoding: 'utf8', windowsHide: true, timeout: timeoutMs });
|
|
18
|
+
return { ok: r?.status === 0, status: r?.status ?? null, output: String(r?.stdout || r?.stderr || r?.error?.message || '').trim().slice(0, 300) };
|
|
19
|
+
} catch (error) {
|
|
20
|
+
return { ok: false, status: null, output: String(error?.message ?? error).slice(0, 300) };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
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).replaceAll("'", "''");
|
|
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
|
+
const PS_ROW_PARTS = [String.raw`^\s*`, String.raw`(\d+)`, String.raw`\s+`, String.raw`(\d+)`, String.raw`\s+`, String.raw`(\S+)`, String.raw`\s+`, '(.*)', '$'];
|
|
45
|
+
const PS_ROW = new RegExp(PS_ROW_PARTS.join(''));
|
|
46
|
+
export const processRowsOfPs = (text, cmdMax = 4000) => String(text ?? '').split(/\r?\n/).map((line) => PS_ROW.exec(line)).filter(Boolean)
|
|
47
|
+
.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 }));
|
|
48
|
+
|
|
49
|
+
// ---- the environment of other processes (process-env.mjs) -------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
// Windows keeps a process's environment block in its PEB: PEB.ProcessParameters (+0x20) -> Environment (+0x80) and
|
|
52
|
+
// EnvironmentSize (+0x3F0), x64 only. A same-user process opens with PROCESS_QUERY_INFORMATION | PROCESS_VM_READ (0x0410).
|
|
53
|
+
export const PROCESS_ENV_NATIVE = String.raw`
|
|
54
|
+
using System;
|
|
55
|
+
using System.Collections.Generic;
|
|
56
|
+
using System.Runtime.InteropServices;
|
|
57
|
+
using System.Text;
|
|
58
|
+
public static class StarciProcessEnv {
|
|
59
|
+
[StructLayout(LayoutKind.Sequential)] struct PBI { public IntPtr Reserved1; public IntPtr PebBaseAddress; public IntPtr Reserved2a; public IntPtr Reserved2b; public IntPtr UniqueProcessId; public IntPtr Reserved3; }
|
|
60
|
+
[DllImport("ntdll.dll")] static extern int NtQueryInformationProcess(IntPtr h, int cls, ref PBI pbi, int len, out int ret);
|
|
61
|
+
[DllImport("kernel32.dll")] static extern bool ReadProcessMemory(IntPtr h, IntPtr addr, byte[] buf, IntPtr size, out IntPtr read);
|
|
62
|
+
[DllImport("kernel32.dll")] static extern IntPtr OpenProcess(int access, bool inherit, int pid);
|
|
63
|
+
[DllImport("kernel32.dll")] static extern bool CloseHandle(IntPtr h);
|
|
64
|
+
[DllImport("kernel32.dll")] static extern bool IsWow64Process(IntPtr h, out bool wow);
|
|
65
|
+
static byte[] Read(IntPtr h, IntPtr addr, int size) {
|
|
66
|
+
var buf = new byte[size]; IntPtr n;
|
|
67
|
+
if (!ReadProcessMemory(h, addr, buf, (IntPtr)size, out n) || (int)n != size) return null;
|
|
68
|
+
return buf;
|
|
69
|
+
}
|
|
70
|
+
public static Dictionary<string, string> Env(int pid) {
|
|
71
|
+
var h = OpenProcess(0x0410, false, pid);
|
|
72
|
+
if (h == IntPtr.Zero) return null;
|
|
73
|
+
try { return EnvHandle(h); } finally { CloseHandle(h); }
|
|
74
|
+
}
|
|
75
|
+
public static Dictionary<string, string> EnvHandle(IntPtr h) {
|
|
76
|
+
bool wow; if (IsWow64Process(h, out wow) && wow) return null;
|
|
77
|
+
var pbi = new PBI(); int ret;
|
|
78
|
+
if (NtQueryInformationProcess(h, 0, ref pbi, Marshal.SizeOf(pbi), out ret) != 0) return null;
|
|
79
|
+
var peb = Read(h, pbi.PebBaseAddress + 0x20, 8); if (peb == null) return null;
|
|
80
|
+
var parameters = (IntPtr)BitConverter.ToInt64(peb, 0);
|
|
81
|
+
var envPtr = Read(h, parameters + 0x80, 8); if (envPtr == null) return null;
|
|
82
|
+
var sizeBuf = Read(h, parameters + 0x3F0, 8); if (sizeBuf == null) return null;
|
|
83
|
+
long size = BitConverter.ToInt64(sizeBuf, 0);
|
|
84
|
+
if (size <= 0 || size > 4 * 1024 * 1024) return null;
|
|
85
|
+
var block = Read(h, (IntPtr)BitConverter.ToInt64(envPtr, 0), (int)size); if (block == null) return null;
|
|
86
|
+
var d = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
87
|
+
foreach (var entry in Encoding.Unicode.GetString(block).Split('\0')) {
|
|
88
|
+
if (entry.Length == 0) continue;
|
|
89
|
+
int i = entry.IndexOf('=', 1); if (i < 0) continue;
|
|
90
|
+
d[entry.Substring(0, i)] = entry.Substring(i + 1);
|
|
91
|
+
}
|
|
92
|
+
return d;
|
|
93
|
+
}
|
|
94
|
+
}`;
|
|
95
|
+
|
|
96
|
+
/** The PowerShell script reading `names` from the environment of `pids` (every process when null): JSON [{pid, readable, values}]. */
|
|
97
|
+
export function processEnvScript({ names, pids = null }) {
|
|
98
|
+
const wanted = JSON.stringify([...names].map(String));
|
|
99
|
+
const ids = pids ? `@(${[...pids].map(Number).filter(Number.isInteger).join(',')})` : '(Get-Process).Id';
|
|
100
|
+
return [
|
|
101
|
+
`Add-Type -TypeDefinition @'${PROCESS_ENV_NATIVE}\n'@ -Language CSharp`,
|
|
102
|
+
`$names = '${psQuote(wanted)}' | ConvertFrom-Json`,
|
|
103
|
+
`$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 } }`,
|
|
104
|
+
'@($rows) | ConvertTo-Json -Compress -Depth 4',
|
|
105
|
+
].join('\n');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The rows of that script's JSON. */
|
|
109
|
+
export const processEnvRowsOfJson = (text) => {
|
|
110
|
+
const t = String(text ?? '').trim();
|
|
111
|
+
const v = t ? JSON.parse(t) : [];
|
|
112
|
+
return (Array.isArray(v) ? v : [v]).map((r) => ({ pid: Number(r.pid), readable: r.readable === true, values: r.values ?? {} }));
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/** The spawnSync-shaped result of a system tool the host does not carry (status null + error), for a caller that returns a spawn result. `refusal`: a systemTool refusal. */
|
|
116
|
+
export const toolUnavailableResult = (refusal) => ({ status: null, stdout: '', stderr: '', error: Object.assign(new Error(`${refusal.tool} is not in its fixed system location on ${refusal.platform}`), { code: refusal.code }) });
|
|
117
|
+
|
|
118
|
+
/** Promise<string | null>: stdout of one system tool run without blocking the calling thread (hidden window, 1 MiB), or null when the tool is absent, failed or timed out. `tool`: systemTool; `exec`: execFile. */
|
|
119
|
+
export const execSystemTool = (name, args, timeout, { exec, tool }) => new Promise((resolve) => {
|
|
120
|
+
try {
|
|
121
|
+
const found = tool(name);
|
|
122
|
+
if (!found.ok) { resolve(null); return; }
|
|
123
|
+
exec(found.path, args, { timeout, windowsHide: true, maxBuffer: 1 << 20 }, (error, stdout) => resolve(error ? null : String(stdout)));
|
|
124
|
+
} catch { resolve(null); }
|
|
125
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// probe-guard-command.mjs — run a registered guard hook command the way an agent host runs it, and say whether it resolves.
|
|
2
|
+
//
|
|
3
|
+
// The host runs a hook command through a shell and treats a failure to start it as non-blocking, so an unresolvable
|
|
4
|
+
// guard command leaves every guarded command unguarded without any error the seat acts on. The probe runs the exact
|
|
5
|
+
// command text, with the empty request `{}` the guard allows, once through each shell the host family uses: bash (Git Bash
|
|
6
|
+
// on Windows, the shell Claude Code runs hooks in) and cmd on Windows, sh on POSIX. The environment is the caller's without
|
|
7
|
+
// the seat identity and without the per-user launcher directory on PATH, so only the command's own spelling can resolve.
|
|
8
|
+
// A shell the machine does not carry is skipped with its reason; the probe is ok when every shell that ran exited 0.
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { readEnv } from '../../lib/env.mjs';
|
|
13
|
+
import { withoutSeatEnv } from '../../lib/seat-env.mjs';
|
|
14
|
+
import { resolveRealTool } from './resolve-real-tool.mjs';
|
|
15
|
+
import { runProgram } from './run-program.mjs';
|
|
16
|
+
import { systemTool } from './system-tool.mjs';
|
|
17
|
+
|
|
18
|
+
const PROBE_REQUEST = '{}';
|
|
19
|
+
const PROBE_TIMEOUT_MS = 30_000;
|
|
20
|
+
const DETAIL_CHARS = 200;
|
|
21
|
+
|
|
22
|
+
/** `env` without the per-user launcher directory on its PATH entry. */
|
|
23
|
+
function withoutLauncherDir(env, { home, platform }) {
|
|
24
|
+
const key = Object.keys(env).find((name) => name.toLowerCase() === 'path');
|
|
25
|
+
if (!key) return env;
|
|
26
|
+
const comparable = (entry) => (platform === 'win32' ? path.resolve(entry).toLowerCase() : path.resolve(entry));
|
|
27
|
+
const launcher = comparable(path.join(home, '.starci', 'bin'));
|
|
28
|
+
const kept = String(env[key]).split(platform === 'win32' ? ';' : ':').filter((entry) => entry && comparable(entry.replace(/^"(.*)"$/, '$1')) !== launcher);
|
|
29
|
+
return { ...env, [key]: kept.join(platform === 'win32' ? ';' : ':') };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The Git Bash a Windows seat runs hooks in: Claude Code's own override, else the `bin/bash.exe` of the Git installation `git` resolves to; null when none. */
|
|
33
|
+
function gitBashOf({ env, home, platform, exists }) {
|
|
34
|
+
const named = readEnv('CLAUDE_CODE_GIT_BASH_PATH', env);
|
|
35
|
+
if (named) return exists(named) ? named : null;
|
|
36
|
+
const git = resolveRealTool('git', { env, home, platform });
|
|
37
|
+
if (!git) return null;
|
|
38
|
+
const root = path.dirname(path.dirname(git));
|
|
39
|
+
return [root, path.dirname(root)].map((dir) => path.join(dir, 'bin', 'bash.exe')).find((file) => exists(file)) ?? null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The shells this host family runs hooks in: [{shell, file (null when absent), argv(command), verbatim}]. */
|
|
43
|
+
function shellsOf({ env, home, platform, exists }) {
|
|
44
|
+
const toolPath = (name) => { const found = systemTool(name, { platform, env, exists }); return found.ok ? found.path : null; };
|
|
45
|
+
if (platform !== 'win32') return [{ shell: 'sh', file: toolPath('sh'), argv: (command) => ['-c', command], verbatim: false }];
|
|
46
|
+
return [
|
|
47
|
+
{ shell: 'cmd', file: toolPath('cmd'), argv: (command) => ['/d', '/s', '/c', `"${command}"`], verbatim: true },
|
|
48
|
+
{ shell: 'bash', file: gitBashOf({ env, home, platform, exists }), argv: (command) => ['-c', command], verbatim: false },
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const detailOf = (result) => {
|
|
53
|
+
const text = String(result.stderr || result.stdout || result.error?.message || '').trim().replace(/\s+/g, ' ');
|
|
54
|
+
return text.slice(0, DETAIL_CHARS);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Run `command` through every shell of the platform. Returns {ok, reason, shells: [{shell, ok, skipped?, status?, detail?}]};
|
|
59
|
+
* `reason` is null when ok, else one line naming each failing shell with its exit status and output.
|
|
60
|
+
* Seams: `exists` (file probe), `run` (the spawn).
|
|
61
|
+
*/
|
|
62
|
+
export function probeGuardCommand({ command, home = os.homedir(), platform = process.platform, env = process.env, exists = fs.existsSync, run = runProgram } = {}) {
|
|
63
|
+
const probeEnv = withoutLauncherDir(withoutSeatEnv(env), { home, platform });
|
|
64
|
+
const shells = shellsOf({ env, home, platform, exists }).map((spec) => {
|
|
65
|
+
if (!spec.file) return { shell: spec.shell, ok: true, skipped: `${spec.shell} is not installed on this machine` };
|
|
66
|
+
const result = run(spec.file, spec.argv(command), { input: PROBE_REQUEST, env: probeEnv, timeout: PROBE_TIMEOUT_MS, ...(spec.verbatim ? { windowsVerbatimArguments: true } : {}) });
|
|
67
|
+
return { shell: spec.shell, ok: result.status === 0, status: result.status, detail: detailOf(result) };
|
|
68
|
+
});
|
|
69
|
+
const ran = shells.filter((entry) => !entry.skipped);
|
|
70
|
+
const failed = ran.filter((entry) => !entry.ok);
|
|
71
|
+
const ok = ran.length > 0 && failed.length === 0;
|
|
72
|
+
const reason = ok ? null : (failed.map((entry) => `${entry.shell} exit ${entry.status}: ${entry.detail}`).join('; ') || 'no shell on this machine can run the guard command');
|
|
73
|
+
return { ok, reason, shells };
|
|
74
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// process-list.mjs — the synchronous host process-table read.
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { processListScript, processRowsOfJson, processRowsOfPs } from './lib.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Synchronously read the host process table for caller-owned filtering.
|
|
7
|
+
* `where` is a Windows WQL filter; `cpu` requests Windows per-core CPU values.
|
|
8
|
+
* Other platforms use ps, with `exe`, `created` and `ws` left null. `cmdMax`
|
|
9
|
+
* truncates command lines before the optional `match` RegExp filters rows.
|
|
10
|
+
* Returns process rows, including an empty list, or null on query or parsing
|
|
11
|
+
* failure. A caller must treat null as an unreadable table rather than no processes.
|
|
12
|
+
*/
|
|
13
|
+
export function processList({ where = null, match = null, cmdMax = 4000, cpu = false, run = spawnSync, platform = process.platform, timeoutMs = 120_000 } = {}) {
|
|
14
|
+
try {
|
|
15
|
+
let rows;
|
|
16
|
+
if (platform === 'win32') {
|
|
17
|
+
const r = run('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', processListScript({ where, cmdMax, cpu })],
|
|
18
|
+
{ encoding: 'utf8', windowsHide: true, timeout: timeoutMs, maxBuffer: 256 * 1024 * 1024 });
|
|
19
|
+
if (!r || r.error || r.status !== 0) return null;
|
|
20
|
+
rows = processRowsOfJson(r.stdout);
|
|
21
|
+
} else {
|
|
22
|
+
const r = run('ps', ['-eo', 'pid=,ppid=,comm=,args='], { encoding: 'utf8', timeout: Math.min(timeoutMs, 30_000) });
|
|
23
|
+
if (!r || r.error || r.status !== 0) return null;
|
|
24
|
+
rows = processRowsOfPs(r.stdout, cmdMax);
|
|
25
|
+
}
|
|
26
|
+
return match ? rows.filter((p) => match.test(p.cmd)) : rows;
|
|
27
|
+
} catch { return null; }
|
|
28
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// system-tool.mjs — the absolute path of one OS system tool, taken from FIXED directories no ordinary user can write:
|
|
2
|
+
// Windows <SystemRoot>\System32 (and the NVIDIA driver's own folders), POSIX /usr/bin, /bin, /usr/sbin, /sbin. A tool is
|
|
3
|
+
// never looked up through PATH, so a writable directory early on PATH cannot stand in for it. The process call files
|
|
4
|
+
// spawn the returned path; a tool the host does not carry is a typed refusal, which each caller turns into its own
|
|
5
|
+
// degraded answer (null, "unknown"), never a crash.
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { readEnv } from '../../lib/env.mjs';
|
|
9
|
+
|
|
10
|
+
const DEFAULT_SYSTEM_ROOT = ['C:', 'Windows'].join(path.win32.sep);
|
|
11
|
+
const POSIX_DIRS = ['/usr/bin', '/bin', '/usr/sbin', '/sbin'];
|
|
12
|
+
// Windows: path segments under <SystemRoot>\System32 (nvidia-smi also under <drive>\Program Files\NVIDIA Corporation\NVSMI, its legacy home).
|
|
13
|
+
const WINDOWS_TOOLS = {
|
|
14
|
+
cmd: [['cmd.exe']], powershell: [['WindowsPowerShell', 'v1.0', 'powershell.exe']], schtasks: [['schtasks.exe']], netstat: [['NETSTAT.EXE']], tasklist: [['tasklist.exe']],
|
|
15
|
+
'nvidia-smi': [['nvidia-smi.exe'], ['..', '..', 'Program Files', 'NVIDIA Corporation', 'NVSMI', 'nvidia-smi.exe']],
|
|
16
|
+
};
|
|
17
|
+
const POSIX_TOOLS = { sh: 'sh', lsof: 'lsof', ps: 'ps', 'nvidia-smi': 'nvidia-smi' };
|
|
18
|
+
|
|
19
|
+
const refusal = (tool, platform) => ({ ok: false, code: 'SYSTEM_TOOL_UNAVAILABLE', tool, platform });
|
|
20
|
+
|
|
21
|
+
/** {ok: true, path} (absolute, existing) or the typed refusal {ok: false, code: 'SYSTEM_TOOL_UNAVAILABLE', tool, platform}. `exists` is the file probe. */
|
|
22
|
+
export function systemTool(tool, { platform = process.platform, env = process.env, exists = (file) => fs.existsSync(file) } = {}) {
|
|
23
|
+
if (platform === 'win32') {
|
|
24
|
+
const root = readEnv('SystemRoot', env) ?? readEnv('SYSTEMROOT', env) ?? readEnv('windir', env) ?? readEnv('WINDIR', env) ?? DEFAULT_SYSTEM_ROOT;
|
|
25
|
+
const system32 = path.win32.join(root, 'System32');
|
|
26
|
+
for (const rel of Object.hasOwn(WINDOWS_TOOLS, tool) ? WINDOWS_TOOLS[tool] : []) {
|
|
27
|
+
const file = path.win32.join(system32, ...rel);
|
|
28
|
+
if (exists(file)) return { ok: true, path: file };
|
|
29
|
+
}
|
|
30
|
+
return refusal(tool, platform);
|
|
31
|
+
}
|
|
32
|
+
for (const dir of Object.hasOwn(POSIX_TOOLS, tool) ? POSIX_DIRS : []) {
|
|
33
|
+
const file = path.posix.join(dir, POSIX_TOOLS[tool]);
|
|
34
|
+
if (exists(file)) return { ok: true, path: file };
|
|
35
|
+
}
|
|
36
|
+
return refusal(tool, platform);
|
|
37
|
+
}
|