@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,468 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// command-guard.mjs — the op guard as a PreToolUse hook: the shell command an op or [Worker] agent is about to run is
|
|
3
|
+
// checked before it runs, and a refused one never starts (exit 2, the reason on stderr - the block every host speaks:
|
|
4
|
+
// Claude Code, Codex and Devin).
|
|
5
|
+
//
|
|
6
|
+
// Every agent launches through Orca worker-start, which owns the agent's environment, so a guard can no longer ride
|
|
7
|
+
// on the agent's PATH. The launch binds the op's guard to its Orca terminal instead (scripts/guards/hook-install.mjs
|
|
8
|
+
// bindGuardTerminal -> <guards root>/terminals/<handle>.json) and launch trust registers this hook in the launch
|
|
9
|
+
// worktree's PROJECT settings (scripts/agent/trust.mjs projectTargets: .claude/settings.local.json,
|
|
10
|
+
// .devin/config.local.json) and, for Codex, in the config.toml of the managed Codex home (Codex lists no hook of a
|
|
11
|
+
// linked worktree's project layer), never a user-global settings file of the owner's own tools. The Kernel's launch binds a guard
|
|
12
|
+
// of role 'kernel' the same way (scripts/kernel/start-workflow.mjs, contract change kernel-guard-file): its raw shell
|
|
13
|
+
// commands meet every rule below, and its `node cli.mjs <verb>` calls pass. A session with no Orca terminal, or whose
|
|
14
|
+
// terminal has no guard bound (the [Supervisor], a lane, the owner's own sessions), meets ONE rule only, wherever this
|
|
15
|
+
// hook is registered: an install through a linked node_modules (below), which no caller ever means to run.
|
|
16
|
+
//
|
|
17
|
+
// What it refuses, each from a real incident:
|
|
18
|
+
// - git: the shared-checkout policy (git-policy.mjs classifyGit - history rewrites, sweeping discards, foreign
|
|
19
|
+
// pathspecs, worktrees, hook bypasses); an App Router pathspec whose glob reading reaches another path.
|
|
20
|
+
// - installs: an install-family command of npm, pnpm or yarn (ci, install/i, add, uninstall, prune, ...; yarn alone)
|
|
21
|
+
// whose package root (cwd, --prefix, -C/--dir, --cwd) has a node_modules - its own, one it sits inside, or its
|
|
22
|
+
// workspace root's - that is a junction or symlink: it empties the live tree the link points to (deps-guard.mjs
|
|
23
|
+
// linkedNodeModulesOf; DEPS_THROUGH_LINK, contract change install-through-link; the third wipe, 2026-10-01, emptied
|
|
24
|
+
// main's packages/grammar/node_modules). Guard file or not. With a guard, also an npm clean install while another
|
|
25
|
+
// workflow's job is leased on the ledger (peerLeasedJobs).
|
|
26
|
+
// - the Kernel's mailbox: `orca orchestration check` from a guard of role kernel (KERNEL_ORCA_CHECK): its --ack
|
|
27
|
+
// consumes deliveries before the ledger records them; the Kernel reads through `starci kernel messages` / `starci kernel questions`
|
|
28
|
+
// and the runtime drains. Ops and [Worker]s keep it: Orca's worker protocol (modules/host/orca/api.yaml
|
|
29
|
+
// operationAgent) has them check their own Run's deliveries, which no ledger record depends on.
|
|
30
|
+
// - the environment: a command that writes the whole environment to output (env, printenv, bare set, export -p, declare -x,
|
|
31
|
+
// Get-ChildItem env:, [Environment]::GetEnvironmentVariables(), node -p process.env, python -c print(os.environ)), even
|
|
32
|
+
// filtered by grep or redacted by sed (ENV_DUMP, scripts/guards/env-dump-verdict.mjs); one named variable is read freely.
|
|
33
|
+
// - links: `ln`, `mklink`, New-Item -ItemType Junction|SymbolicLink|HardLink and [IO.Directory]::Create*Link - an
|
|
34
|
+
// op never creates a link.
|
|
35
|
+
// - processes: a kill by image name or pattern (taskkill /IM or /FI, pkill, killall, Stop-Process -Name, wmic process
|
|
36
|
+
// where name=...), which also ends Orca and every other agent's processes of that name (a lane's taskkill of node.exe
|
|
37
|
+
// restarted Orca, 2026-10-01); a kill whose targets come from a process query (Get-CimInstance Win32_Process,
|
|
38
|
+
// Get-Process, ps | grep, pgrep, then Stop-Process, kill, .Terminate()); an agent ends only the PIDs it started
|
|
39
|
+
// (scripts/guards/process-kill-verdict.mjs).
|
|
40
|
+
// - deletes: a recursive delete (rm -r, rmdir|rd /s, del /s, robocopy /MIR|/PURGE, Remove-Item -Recurse and its
|
|
41
|
+
// aliases), which follows a junction into the live tree; trees go through safeRemove.
|
|
42
|
+
// - workflow history: inside the workflow worktree its guard names (guard.workflowWorktree, contract change
|
|
43
|
+
// workflow-worktree), a git command that rewrites history or a ref, or discards tracked work (merge, rebase,
|
|
44
|
+
// push, pull, cherry-pick, revert, am, reset to a revision or with a mode, checkout of a branch or a path, switch,
|
|
45
|
+
// restore of the working tree, stash, tag/branch writes, update-ref, filter-branch, replace, reflog expire|delete,
|
|
46
|
+
// notes writes). Scoped commits run hooks and are adopted at settle (WORKFLOW_HISTORY_CHANGE). Reads
|
|
47
|
+
// (status, diff, log, show, ...) pass, and nothing changes outside that worktree.
|
|
48
|
+
// - launches: `orca terminal create` and a headless agent CLI (codex exec, claude|cursor-agent|devin -p/--print,
|
|
49
|
+
// gemini -p, opencode run) - an agent Orca does not supervise; every agent starts through orca orchestration
|
|
50
|
+
// worker-start. A heredoc body, an echo or a commit message that only mentions a command runs nothing and passes.
|
|
51
|
+
// - role rights (rights.mjs, rules R223 RIGHTS_ROLE_DENIED and R224 RIGHTS_PROTECTED_ZONE): by the caller's role - the job guard,
|
|
52
|
+
// the seat guard or STARCI_ROLE, never a claim in the command - a push, a tag, a publish, an op's commit, a whole-suite
|
|
53
|
+
// run, a clean install without the host lock and a release cut are refused, and a supervisor self-upgrade never writes the
|
|
54
|
+
// protected zone (modules/kernel/protected-zone.yaml) nor an op the .claude runtime checkout: Edit, Write, MultiEdit and
|
|
55
|
+
// NotebookEdit calls are read next to the shell text.
|
|
56
|
+
// Before an allowed git command, a stale shared .git/index.lock is recovered (scripts/machine/lock-recovery.mjs preflightIndexLock).
|
|
57
|
+
// A bound caller refuses incomplete input and evaluation faults; an unbound shell retains its fail-open behavior.
|
|
58
|
+
import fs from 'node:fs';
|
|
59
|
+
import path from 'node:path';
|
|
60
|
+
import { fileURLToPath } from 'node:url';
|
|
61
|
+
import { pathKey } from '../lib/path-key.mjs';
|
|
62
|
+
import { launchVerdict } from './launch-verdict.mjs';
|
|
63
|
+
import { envDumpVerdict } from './env-dump-verdict.mjs';
|
|
64
|
+
import { nameKillVerdict, queryKillVerdict } from './process-kill-verdict.mjs';
|
|
65
|
+
import { installLinkVerdict, installVerdict, kernelMailboxVerdict } from './install-verdict.mjs';
|
|
66
|
+
import { isMain } from '../lib/is-main.mjs';
|
|
67
|
+
import { readEnv } from '../lib/env.mjs';
|
|
68
|
+
import { hookErrorMessage, readInput, writeHookOutput } from './hook-io.mjs';
|
|
69
|
+
import { fileRightsVerdict, policyToolVerdict, rightsOfCall } from './call-rights.mjs';
|
|
70
|
+
import { boundGuard, boundSeat, gitSubOf, redirectTargetsOf, rightsRoleOf } from './rights.mjs';
|
|
71
|
+
import { intrinsicPolicyRead, loadCommandPolicy, policyVerdict } from './command-policy.mjs';
|
|
72
|
+
import { commandsOf, programOf } from './shell-commands.mjs';
|
|
73
|
+
import { tempPath } from '../api/fs/temp-path.mjs';
|
|
74
|
+
import { findInOrder } from '../lib/in-order.mjs';
|
|
75
|
+
import { criticReadVerdict } from './critic-reach.mjs';
|
|
76
|
+
import { runHookVerdict } from './verdict-process.mjs';
|
|
77
|
+
import { receiveVerdict } from './hook-verdict.mjs';
|
|
78
|
+
const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
79
|
+
export { boundGuard } from './rights.mjs';
|
|
80
|
+
|
|
81
|
+
export { commandsOf, programOf, simpleCommands } from './shell-commands.mjs';
|
|
82
|
+
|
|
83
|
+
/* ---------------------------------------------------------------- verdicts */
|
|
84
|
+
|
|
85
|
+
const LINK_ITEM_TYPE = /^(?:junction|symboliclink|hardlink)$/i;
|
|
86
|
+
const LINK_API = /::\s*create(?:symbolic|hard)link\b/i;
|
|
87
|
+
const linkVerdict = (program, args, word) => {
|
|
88
|
+
const refused = program === 'ln' || program === 'mklink'
|
|
89
|
+
|| ((program === 'new-item' || program === 'ni') && args.some((a, i) => (/^-(?:itemtype|type|it)$/i.test(args[i - 1] ?? '') && LINK_ITEM_TYPE.test(a)) || /^-(?:itemtype|type|it):(?:junction|symboliclink|hardlink)$/i.test(a)))
|
|
90
|
+
|| LINK_API.test(word) || args.some((a) => LINK_API.test(a));
|
|
91
|
+
if (!refused) return null;
|
|
92
|
+
return { code: 'LINK_CREATE', command: [word, ...args].join(' ').slice(0, 200),
|
|
93
|
+
reason: 'an op worker never creates a junction, symlink or hard link - a link from a scratch tree into a live repository is followed by a recursive delete (git worktree remove, rm -rf, Remove-Item) and empties the live repository',
|
|
94
|
+
remedy: 'work in your dispatched checkout with its own node_modules; a need for another tree or a linked dependency is reported (report blocked environment), never made' };
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// A recursive delete follows a junction or symlink inside the tree and empties the live tree it points at (a Git for
|
|
98
|
+
// Windows worktree removal deleted 674 live files through node_modules junctions; a later incident emptied main's
|
|
99
|
+
// node_modules the same way). The runtime's safeRemove removes every link as a
|
|
100
|
+
// link first. One junction is removed on its own with `cmd /c rmdir <path>` (no /s), which removes the link only.
|
|
101
|
+
const REMOVE_ITEM = new Set(['remove-item', 'ri', 'rm', 'del', 'erase', 'rd', 'rmdir']);
|
|
102
|
+
// PowerShell accepts any unambiguous prefix of -Recurse (then an optional :value other than false).
|
|
103
|
+
const POWERSHELL_RECURSE = /^-(?:r|re|rec|recu|recur|recurs|recurse)(?::(?!\$?false$).*)?$/i;
|
|
104
|
+
const cmdSwitches = (arg) => (/^\/\/?[a-z](?:\/[a-z])*$/i.test(arg) ? arg.toLowerCase().split('/').filter(Boolean) : []);
|
|
105
|
+
const hasCmdSwitch = (args, letter) => args.some((a) => cmdSwitches(a).includes(letter));
|
|
106
|
+
// The recursive-delete form of one program, or null: `${program} -Recurse`, 'rm -r', 'cmd /s' or the robocopy flag.
|
|
107
|
+
const powershellRecursiveHow = (program, options) => options.some((a) => POWERSHELL_RECURSE.test(a) || /^-(?:rf|fr)$/i.test(a)) ? `${program} -Recurse` : null;
|
|
108
|
+
const recursiveHowForProgram = (program, options) => {
|
|
109
|
+
if (program === 'remove-item' || program === 'ri')
|
|
110
|
+
return options.some((a) => POWERSHELL_RECURSE.test(a)) ? `${program} -Recurse` : null;
|
|
111
|
+
if (program === 'rm')
|
|
112
|
+
return options.some((a) => a === '--recursive' || (/^-[A-Za-z]+$/.test(a) && /r/i.test(a))) ? 'rm -r' : null;
|
|
113
|
+
if (['rmdir', 'rd', 'del', 'erase'].includes(program))
|
|
114
|
+
return hasCmdSwitch(options, 's') ? `${program} /s` : null;
|
|
115
|
+
if (program === 'robocopy') {
|
|
116
|
+
const flag = options.find((a) => /^(?:\/\/?|-)(?:mir|purge)$/i.test(a));
|
|
117
|
+
return flag ? `robocopy ${flag.replace(/^\/\/|^-/, '/').toUpperCase()}` : null;
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
};
|
|
121
|
+
const recursiveHow = (program, options, dialect) => dialect === 'powershell' && REMOVE_ITEM.has(program)
|
|
122
|
+
? powershellRecursiveHow(program, options) : recursiveHowForProgram(program, options);
|
|
123
|
+
const recursiveDeleteVerdict = (program, args, dialect) => {
|
|
124
|
+
const options = args.includes('--') ? args.slice(0, args.indexOf('--')) : args;
|
|
125
|
+
const how = recursiveHow(program, options, dialect);
|
|
126
|
+
if (!how) return null;
|
|
127
|
+
return { code: 'RECURSIVE_DELETE', command: [program, ...args].join(' ').slice(0, 200),
|
|
128
|
+
reason: `${how} deletes a tree recursively and follows every junction or symlink inside it into the live tree it points at (a worktree removal through node_modules junctions deleted 674 live files)`,
|
|
129
|
+
remedy: 'remove a tree only through the runtime\'s safeRemove (scripts/api/fs/safe-remove.mjs), which removes every link as a link first; remove one junction with `cmd /c rmdir <path>` (no /s); a single file with `rm <file>`' };
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// The workflow branch stays append-only: the worker's scoped hook-checked commit is adopted at settle;
|
|
133
|
+
// preserveAndReset keeps a failed op's work. The guard refuses rewrites, ref moves and discarded tracked work.
|
|
134
|
+
const WORKFLOW_HISTORY_VERBS = new Set(['merge', 'rebase', 'push', 'pull', 'cherry-pick', 'revert', 'am', 'update-ref', 'switch', 'filter-branch', 'replace']);
|
|
135
|
+
const RESET_MODES = new Set(['--hard', '--soft', '--mixed', '--merge', '--keep']);
|
|
136
|
+
const BRANCH_WRITE_FLAGS = new Set(['-d', '-D', '-m', '-M', '-c', '-C', '-f', '-u', '--delete', '--move', '--copy', '--force', '--unset-upstream', '--edit-description', '--no-track']);
|
|
137
|
+
const branchWriteValue = (option, name) => option === name || (option.startsWith(`${name}=`)
|
|
138
|
+
&& !/[\n\r\u2028\u2029]/.test(option.slice(name.length + 1)));
|
|
139
|
+
const isBranchWrite = option => BRANCH_WRITE_FLAGS.has(option) || branchWriteValue(option, '--set-upstream-to') || branchWriteValue(option, '--track');
|
|
140
|
+
const BRANCH_READ_WORDS = new Set(['--list', '--all', '--remotes', '--show-current', '--contains', '--no-contains', '--merged', '--no-merged', '--points-at', '--sort', '--format', '--color', '--no-color', '--column', '--no-column', '-vv', '--verbose']);
|
|
141
|
+
const BRANCH_READ_VALUES = /^--(?:sort|format|color|column)=/;
|
|
142
|
+
const branchRead = (o) => BRANCH_READ_WORDS.has(o) || BRANCH_READ_VALUES.test(o) || /^-[avrl]+$/.test(o);
|
|
143
|
+
const insideDir = (dir, root) => { const d = pathKey(dir); const r = pathKey(root); return d === r || d.startsWith(`${r}/`); };
|
|
144
|
+
|
|
145
|
+
// Why each history-adjacent sub changes history, a ref or tracked work, or null when it only reads.
|
|
146
|
+
const SUB_CHANGE = {
|
|
147
|
+
reset: (options, words) => (options.some((o) => RESET_MODES.has(o)) || words.length ? 'git reset to a revision or with a mode moves HEAD or discards tracked work' : null),
|
|
148
|
+
checkout: (options, words, before, dd) => (before.length || dd !== -1 ? 'git checkout switches the branch or discards tracked work' : null),
|
|
149
|
+
restore: (options) => (options.some((o) => o === '--worktree' || o === '-W') || !options.some((o) => o === '--staged' || o === '-S') ? 'git restore discards tracked work in the working tree' : null),
|
|
150
|
+
stash: (options, words) => (['list', 'show'].includes(words[0]) ? null : 'git stash takes tracked work out of the tree'),
|
|
151
|
+
tag: (options, words) => {
|
|
152
|
+
if (words.length && !options.some((o) => o === '-l' || o === '--list')) return 'git tag writes a ref';
|
|
153
|
+
return options.some((o) => o === '-d' || o === '--delete' || o === '-f' || o === '--force') ? 'git tag writes a ref' : null;
|
|
154
|
+
},
|
|
155
|
+
branch: (options, words) => {
|
|
156
|
+
if (options.some(isBranchWrite)) return 'git branch writes a ref';
|
|
157
|
+
return words.length && !options.some((o) => branchRead(o)) ? 'git branch creates a ref' : null;
|
|
158
|
+
},
|
|
159
|
+
reflog: (options, words) => (['expire', 'delete'].includes(words[0]) ? `git reflog ${words[0]} rewrites ref history` : null),
|
|
160
|
+
notes: (options, words) => (!words[0] || ['list', 'show'].includes(words[0]) ? null : 'git notes writes a ref'),
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/** Why `git <sub> <rest>` changes history, a ref or tracked work, or null when it only reads. */
|
|
164
|
+
function workflowHistoryChange(sub, rest) {
|
|
165
|
+
const dd = rest.indexOf('--');
|
|
166
|
+
const before = dd === -1 ? rest : rest.slice(0, dd);
|
|
167
|
+
const options = before.filter((a) => a.startsWith('-'));
|
|
168
|
+
const words = before.filter((a) => !a.startsWith('-'));
|
|
169
|
+
if (WORKFLOW_HISTORY_VERBS.has(sub)) return `git ${sub} writes history or a ref`;
|
|
170
|
+
return SUB_CHANGE[sub]?.(options, words, before, dd) ?? null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** The WORKFLOW_HISTORY_CHANGE refusal for one git call inside the guard's workflow worktree, or null. */
|
|
174
|
+
function workflowHistoryVerdict({ args, cwd, guard, parseGitArgv }) {
|
|
175
|
+
if (!guard?.workflowWorktree) return null;
|
|
176
|
+
const parsed = parseGitArgv(args, cwd);
|
|
177
|
+
if (!parsed.sub || !insideDir(parsed.cwd, guard.workflowWorktree)) return null;
|
|
178
|
+
const why = workflowHistoryChange(parsed.sub, parsed.rest);
|
|
179
|
+
if (!why) return null;
|
|
180
|
+
return { tool: 'git', code: 'WORKFLOW_HISTORY_CHANGE', command: args.join(' ').slice(0, 200),
|
|
181
|
+
reason: `${why}, inside the workflow worktree ${pathKey(guard.workflowWorktree)}: ops keep history append-only and preserve tracked work`,
|
|
182
|
+
remedy: 'commit only your owned paths through starci git commit with hooks, then report; the runtime adopts the commit at settle' };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// git reads an App Router segment ([locale], [...slug]) in a pathspec as a character class. The policy scopes it as the
|
|
186
|
+
// literal path admission granted, so the command is refused only when git's glob reading reaches a path the literal
|
|
187
|
+
// reading does not (a sibling like src/app/l/ another workflow owns): the files each reading
|
|
188
|
+
// names are compared with git ls-files. The remedy is the same command with :(literal) pathspecs.
|
|
189
|
+
function appRouterGlob({ args, cwd, deps }) {
|
|
190
|
+
const { literalAppRouterArgv, parseGitArgv } = deps.policy;
|
|
191
|
+
const listFile = tempPath(`starci-pathspec-${process.pid}-${Date.now()}.nul`);
|
|
192
|
+
try {
|
|
193
|
+
const literal = literalAppRouterArgv(args, { cwd, stdin: null, listFile });
|
|
194
|
+
if (!literal.changed) return null;
|
|
195
|
+
const dir = parseGitArgv(args, cwd).cwd;
|
|
196
|
+
const files = (spec) => {
|
|
197
|
+
const r = deps.git.lsFiles(['-c', '-o', '--exclude-standard', '-z', ...spec], { cwd: dir });
|
|
198
|
+
return r.status === 0 ? new Set(r.stdout.split('\0').filter(Boolean)) : null;
|
|
199
|
+
};
|
|
200
|
+
let globbed, named, remedy;
|
|
201
|
+
if (literal.list == null) {
|
|
202
|
+
const pairs = args.map((a, i) => [a, literal.argv[i]]).filter(([a, l]) => a !== l);
|
|
203
|
+
globbed = files(['--', ...pairs.map((p) => p[0])]);
|
|
204
|
+
named = files(['--', ...pairs.map((p) => p[1])]);
|
|
205
|
+
remedy = `name the paths literally: git ${literal.argv.join(' ')}`;
|
|
206
|
+
} else {
|
|
207
|
+
// ls-files takes no pathspec list: the entries are read here and named after `--`.
|
|
208
|
+
const at = args.findIndex((a) => a === '--pathspec-from-file' || a.startsWith('--pathspec-from-file='));
|
|
209
|
+
const listPath = args[at] === '--pathspec-from-file' ? args[at + 1] : args[at].slice('--pathspec-from-file='.length);
|
|
210
|
+
const entries = deps.policy.parsePathspecList(fs.readFileSync(path.resolve(dir, listPath), 'utf8'), args.includes('--pathspec-file-nul'));
|
|
211
|
+
globbed = files(['--', ...entries]);
|
|
212
|
+
named = files(['--', ...literal.list.split('\0').filter(Boolean)]);
|
|
213
|
+
remedy = 'write each entry of the pathspec list as :(literal)<path>';
|
|
214
|
+
}
|
|
215
|
+
const reached = globbed && named ? [...globbed].filter((f) => !named.has(f)) : [];
|
|
216
|
+
if (!reached.length) return null;
|
|
217
|
+
return { code: 'APP_ROUTER_GLOB',
|
|
218
|
+
reason: `an App Router segment in a pathspec is a glob to git: it would also reach ${reached.slice(0, 5).join(', ')}`,
|
|
219
|
+
remedy };
|
|
220
|
+
} finally { fs.rmSync(listFile, { force: true }); }
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function gitVerdict({ args, cwd, env, guard, deps }) {
|
|
224
|
+
const { classifyGit, pathspecListOnStdin } = deps.policy;
|
|
225
|
+
const { revParseQuery, configGet } = deps.git;
|
|
226
|
+
const top = () => { const r = revParseQuery(['--show-toplevel'], { cwd }); return r.status === 0 && r.stdout.trim() ? path.resolve(r.stdout.trim()) : null; };
|
|
227
|
+
const currentConfig = (key) => { const r = configGet(cwd, key); return r.ok ? r.stdout : null; };
|
|
228
|
+
const verdict = classifyGit(args, { cwd, owned: guard?.owned ?? null, top: guard?.owned?.length ? top() : null, env, stdin: null, currentConfig });
|
|
229
|
+
const command = args.join(' ').slice(0, 200);
|
|
230
|
+
if (!verdict.allow) return { tool: 'git', ...verdict, command };
|
|
231
|
+
if (!pathspecListOnStdin(args)) { const glob = appRouterGlob({ args, cwd, deps }); if (glob) return { tool: 'git', ...glob, command }; }
|
|
232
|
+
const dashC = args.indexOf('-C');
|
|
233
|
+
await deps.indexLock.preflightIndexLock({ cwd: dashC >= 0 && args[dashC + 1] ? path.resolve(cwd, args[dashC + 1]) : cwd, guard, env, say: deps.say });
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const loadDeps = async () => {
|
|
238
|
+
const [policy, npm, { lsFiles }, { revParseQuery }, { configGet }, indexLock] = await Promise.all([import('./git-policy.mjs'), import('./deps-guard.mjs'), import('../api/git/ls-files.mjs'), import('../api/git/rev-parse-query.mjs'), import('../api/git/config-get.mjs'), import('../machine/lock-recovery.mjs')]);
|
|
239
|
+
return { policy, npm, git: { lsFiles, revParseQuery, configGet }, indexLock, say: (line) => process.stderr.write(`${line}\n`) };
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const NEEDS_HOST_LOCK = /\b(?:npm|pnpm|yarn|bun)\b[^\n]*\b(?:ci|clean-install|install-clean|cit|install-ci-test)\b/;
|
|
243
|
+
|
|
244
|
+
/** A synchronous reader of the host lock owner (loaded only when a command or a release claim needs it), or a null reader. */
|
|
245
|
+
async function lockOwnerReader(env) {
|
|
246
|
+
try {
|
|
247
|
+
const lock = await import('../machine/host-lock.mjs');
|
|
248
|
+
return () => { try { return lock.hostLockOwner({ env }); } catch { return null; } };
|
|
249
|
+
} catch { return () => null; }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** {role, handle, lockOwner, policy}: the caller's role and the shared command policy loaded only for a bound role. */
|
|
253
|
+
async function rightsContext({ guard = null, seat = null, env = process.env, text = '' } = {}) {
|
|
254
|
+
const claimsRelease = String(env?.STARCI_ROLE ?? '').toLowerCase() === 'release';
|
|
255
|
+
const lockOwner = claimsRelease || NEEDS_HOST_LOCK.test(text) ? await lockOwnerReader(env) : () => null;
|
|
256
|
+
const role = rightsRoleOf({ guard, seat, env, lockOwner: claimsRelease ? lockOwner() : null });
|
|
257
|
+
const policy = role && role !== 'release' ? loadCommandPolicy({ root: skillRoot }) : null;
|
|
258
|
+
if (role && role !== 'release' && !policy) throw new Error('the bound caller command policy is unreadable');
|
|
259
|
+
return { role, handle: env?.ORCA_TERMINAL_HANDLE ?? null, lockOwner, policy };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* The first refusal for one shell call, or null: {tool, code, command, reason, remedy}. `dialect` is the text's shell
|
|
265
|
+
* (PowerShell for Claude's PowerShell tool; bash otherwise, which also reads plain Windows command lines).
|
|
266
|
+
*/
|
|
267
|
+
// The synchronous per-command refusals, in rule order: env dump, link create, name kill, recursive delete, launch.
|
|
268
|
+
const syncCommandVerdict = (c, guard) => envDumpVerdict(c)
|
|
269
|
+
?? linkVerdict(c.program, c.args, c.word)
|
|
270
|
+
?? nameKillVerdict(c.program, c.args)
|
|
271
|
+
?? recursiveDeleteVerdict(c.program, c.args, c.dialect)
|
|
272
|
+
?? launchVerdict(c.program, c.args, guard);
|
|
273
|
+
|
|
274
|
+
// push/tag outside an op's workflow tree have no older git-policy refusal. Return the shared role refusal before
|
|
275
|
+
// loading the full git policy/dependency stack; inside the workflow tree, WORKFLOW_HISTORY_CHANGE still wins.
|
|
276
|
+
const pushTagRoleVerdict = (c, ctx, guard) => {
|
|
277
|
+
let gitCwd = c.cwd;
|
|
278
|
+
for (let i = 0; i < c.args.length; i += 1) {
|
|
279
|
+
if (c.args[i] === '-C' && c.args[i + 1]) { i += 1; gitCwd = path.resolve(gitCwd, c.args[i]); }
|
|
280
|
+
}
|
|
281
|
+
if (guard?.workflowWorktree && insideDir(gitCwd, guard.workflowWorktree)) return null;
|
|
282
|
+
const byPolicy = policyVerdict({ role: ctx.role, command: c, guard, handle: ctx.handle, lockOwner: ctx.lockOwner, policy: ctx.policy });
|
|
283
|
+
return byPolicy ? policyToolVerdict(c, byPolicy) : null;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const gitCommandVerdict = async (command, ctx, guard, fullDeps) => {
|
|
287
|
+
if (command.program !== 'git') return null;
|
|
288
|
+
if (['push', 'tag'].includes(gitSubOf(command.args).sub)) {
|
|
289
|
+
const byPolicy = pushTagRoleVerdict(command, ctx, guard);
|
|
290
|
+
if (byPolicy) return byPolicy;
|
|
291
|
+
}
|
|
292
|
+
const loaded = await fullDeps();
|
|
293
|
+
const history = workflowHistoryVerdict({ args: command.args, cwd: command.cwd, guard, parseGitArgv: loaded.policy.parseGitArgv });
|
|
294
|
+
if (history) return history;
|
|
295
|
+
return gitVerdict({ args: command.args, cwd: command.cwd, env: command.env, guard, deps: loaded });
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const packageManagerCommandVerdict = async (command, guard, fullDeps) => {
|
|
299
|
+
if (!['npm', 'pnpm', 'yarn', 'bun'].includes(command.program)) return null;
|
|
300
|
+
const loaded = await fullDeps();
|
|
301
|
+
if (!loaded.npm.PACKAGE_MANAGERS.includes(command.program)) return null;
|
|
302
|
+
return installVerdict({ program: command.program, args: command.args, cwd: command.cwd, guard, deps: loaded });
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
/** The first refusal of one parsed command, in rule order: the synchronous rules, git, the kernel mailbox, the package managers; falsy when none. */
|
|
306
|
+
async function commandRefusal(c, ctx, guard, fullDeps) {
|
|
307
|
+
const sync = syncCommandVerdict(c, guard);
|
|
308
|
+
if (sync) return { tool: c.program, ...sync };
|
|
309
|
+
const gitResult = await gitCommandVerdict(c, ctx, guard, fullDeps);
|
|
310
|
+
if (gitResult) return gitResult;
|
|
311
|
+
const mailbox = kernelMailboxVerdict(c.program, c.args, guard);
|
|
312
|
+
if (mailbox) return { tool: c.program, ...mailbox };
|
|
313
|
+
return packageManagerCommandVerdict(c, guard, fullDeps);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export async function commandVerdict({ command, cwd, guard, env = process.env, dialect = 'bash', deps = null, rights = null }) {
|
|
317
|
+
let d = deps;
|
|
318
|
+
const fullDeps = async () => { d ??= await loadDeps(); return d; };
|
|
319
|
+
const commands = commandsOf(command, { cwd, env, dialect });
|
|
320
|
+
if (guard?.role !== 'critic' && commands.length && commands.every(intrinsicPolicyRead) && !redirectTargetsOf(command, cwd).length) return null;
|
|
321
|
+
const ctx = rights ?? await rightsContext({ guard, env, text: command });
|
|
322
|
+
const byQuery = queryKillVerdict(commands, command);
|
|
323
|
+
if (byQuery) return { tool: 'process-query', ...byQuery };
|
|
324
|
+
let refusal = null;
|
|
325
|
+
await findInOrder(commands, async (c) => {
|
|
326
|
+
refusal = await commandRefusal(c, ctx, guard, fullDeps);
|
|
327
|
+
return Boolean(refusal);
|
|
328
|
+
});
|
|
329
|
+
if (refusal) return refusal;
|
|
330
|
+
return rightsOfCall({ commands, command, cwd, ctx, guard });
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* The rules that hold with no guard file (a terminal no launch bound, a lane, the owner's own session): an install
|
|
335
|
+
* through a linked node_modules only. Loads nothing but deps-guard.mjs.
|
|
336
|
+
*/
|
|
337
|
+
async function unguardedVerdict({ command, cwd, env = process.env, dialect = 'bash', npm = null }) {
|
|
338
|
+
const deps = npm ?? await import('./deps-guard.mjs');
|
|
339
|
+
for (const c of commandsOf(command, { cwd, env, dialect })) {
|
|
340
|
+
if (!deps.PACKAGE_MANAGERS.includes(c.program)) continue;
|
|
341
|
+
const v = installLinkVerdict({ program: c.program, args: c.args, cwd: c.cwd, npm: deps });
|
|
342
|
+
if (v) return v;
|
|
343
|
+
}
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** The shell text of one hook input: Claude's Bash/PowerShell, Codex's Bash and Devin's exec all carry `command`. */
|
|
348
|
+
function shellCallOf(input) {
|
|
349
|
+
const command = input?.tool_input?.command;
|
|
350
|
+
if (typeof command !== 'string' || !command.trim()) return null;
|
|
351
|
+
const cwd = input.tool_input.workdir || input.cwd || process.cwd();
|
|
352
|
+
return { command, cwd: path.resolve(cwd), dialect: /^powershell$/i.test(String(input.tool_name ?? '')) ? 'powershell' : 'bash' };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// A cheap first look for the unguarded case: only a command naming a package manager can be refused without a guard.
|
|
356
|
+
const NAMES_PACKAGE_MANAGER = /\b(?:npm|pnpm|yarn)\b/i;
|
|
357
|
+
|
|
358
|
+
const FILE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
|
|
359
|
+
const READ_TOOLS = new Set(['Read', 'Grep', 'Glob']);
|
|
360
|
+
|
|
361
|
+
/** One file-reading tool call of a hook input: {tool, paths}, or null. A call that names no path reaches its working directory. */
|
|
362
|
+
function readCallOf(input) {
|
|
363
|
+
const tool = String(input?.tool_name ?? '');
|
|
364
|
+
if (!READ_TOOLS.has(tool)) return null;
|
|
365
|
+
const given = input.tool_input ?? {};
|
|
366
|
+
const cwd = path.resolve(input.cwd || process.cwd());
|
|
367
|
+
const named = [given.file_path, given.path, given.notebook_path].filter((value) => typeof value === 'string' && value);
|
|
368
|
+
const pattern = typeof given.pattern === 'string' && tool === 'Glob' ? [path.join(path.resolve(cwd, named[0] ?? '.'), given.pattern)] : [];
|
|
369
|
+
return { tool, paths: [...(named.length ? named.map((one) => path.resolve(cwd, one)) : [cwd]), ...pattern] };
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** One file-writing tool call of a hook input: {tool, filePath, edit: {old, new}, cwd}, or null. */
|
|
373
|
+
function fileCallOf(input) {
|
|
374
|
+
const tool = String(input?.tool_name ?? '');
|
|
375
|
+
if (!FILE_TOOLS.has(tool)) return null;
|
|
376
|
+
const given = input.tool_input ?? {};
|
|
377
|
+
const target = given.file_path ?? given.notebook_path ?? given.path;
|
|
378
|
+
if (typeof target !== 'string' || !target) return null;
|
|
379
|
+
const cwd = path.resolve(input.cwd || process.cwd());
|
|
380
|
+
const edits = Array.isArray(given.edits) ? given.edits : [given];
|
|
381
|
+
const joined = (...keys) => edits.map((e) => keys.map((k) => e?.[k]).find((v) => typeof v === 'string') ?? '').join('\n');
|
|
382
|
+
return { tool, filePath: path.resolve(cwd, target), edit: { old: joined('old_string'), new: joined('new_string', 'new_source', 'content') }, cwd };
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/** The rights-only refusal of a session with a role but no job guard (the Supervisor seat, a STARCI_ROLE session): its commands and the files they write. */
|
|
386
|
+
async function rightsOnlyVerdict({ command, cwd, env, dialect, ctx }) {
|
|
387
|
+
const commands = commandsOf(command, { cwd, env, dialect });
|
|
388
|
+
return rightsOfCall({ commands, command, cwd, ctx, guard: null });
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const unguardedHookVerdict = async (call, { env, deps, ctx }) => {
|
|
392
|
+
if (NAMES_PACKAGE_MANAGER.test(call.command)) {
|
|
393
|
+
const verdict = await unguardedVerdict({ ...call, env, npm: deps?.npm ?? null });
|
|
394
|
+
if (verdict) return { verdict, guard: null, cwd: call.cwd };
|
|
395
|
+
}
|
|
396
|
+
if (!ctx.role) return null;
|
|
397
|
+
const verdict = await rightsOnlyVerdict({ ...call, env, ctx });
|
|
398
|
+
return verdict ? { verdict, guard: null, cwd: call.cwd } : null;
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
/** The hook's decision for one input: {verdict, guard} to refuse (guard null for an unguarded session), else null. */
|
|
402
|
+
async function hookDecision(input, { env = process.env, root = skillRoot, deps = null, bindings = null } = {}) {
|
|
403
|
+
const handle = env.ORCA_TERMINAL_HANDLE;
|
|
404
|
+
const guard = bindings ? bindings.guard : boundGuard(handle, { root, env });
|
|
405
|
+
let seat = null;
|
|
406
|
+
if (bindings) seat = bindings.seat;
|
|
407
|
+
else if (!guard) seat = boundSeat(handle, { root, env });
|
|
408
|
+
const read = readCallOf(input);
|
|
409
|
+
if (read) {
|
|
410
|
+
const role = rightsRoleOf({ guard, seat, env, lockOwner: null });
|
|
411
|
+
const verdict = role === 'critic' ? criticReadVerdict({ paths: read.paths, guard, tool: read.tool }) : null;
|
|
412
|
+
return verdict ? { verdict: { tool: read.tool, ...verdict }, guard, cwd: path.resolve(input.cwd || process.cwd()) } : null;
|
|
413
|
+
}
|
|
414
|
+
const file = fileCallOf(input);
|
|
415
|
+
if (file) {
|
|
416
|
+
// File rights do not consume the command table. A release claim without its lock resolves to owner here; both are
|
|
417
|
+
// unrestricted for files, so no host-lock or YAML import is needed.
|
|
418
|
+
const role = rightsRoleOf({ guard, seat, env, lockOwner: null });
|
|
419
|
+
const verdict = await fileRightsVerdict({ role, filePath: file.filePath, tool: file.tool, edit: file.edit, guard });
|
|
420
|
+
return verdict ? { verdict: { tool: file.tool, ...verdict }, guard, cwd: file.cwd } : null;
|
|
421
|
+
}
|
|
422
|
+
const call = shellCallOf(input);
|
|
423
|
+
if (!call) return null;
|
|
424
|
+
const ctx = await rightsContext({ guard, seat, env, text: call.command });
|
|
425
|
+
if (!guard) return unguardedHookVerdict(call, { env, deps, ctx });
|
|
426
|
+
const verdict = await commandVerdict({ ...call, guard, env, deps, rights: ctx });
|
|
427
|
+
return verdict ? { verdict, guard, cwd: call.cwd } : null;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** The hook entry: read bounded input in-process and evaluate policy in an owned verifier with its own deadline. */
|
|
431
|
+
export async function main({ stdin = process.stdin, stderr = process.stderr, env = process.env, root = skillRoot } = {}) {
|
|
432
|
+
const handle = readEnv('ORCA_TERMINAL_HANDLE', env);
|
|
433
|
+
const guard = boundGuard(handle, { root, env });
|
|
434
|
+
const seat = guard ? null : boundSeat(handle, { root, env });
|
|
435
|
+
const enforced = Boolean(guard || seat || readEnv('STARCI_ROLE', env));
|
|
436
|
+
try {
|
|
437
|
+
const raw = await readInput(stdin);
|
|
438
|
+
// The common case - no guard, no seat, no claimed role and no package manager named - exits before anything heavier than two file reads.
|
|
439
|
+
if (!enforced && !NAMES_PACKAGE_MANAGER.test(raw)) return 0;
|
|
440
|
+
const input = JSON.parse(raw);
|
|
441
|
+
// An extra command field on a file/read tool cannot select the shell optimization before its rights check.
|
|
442
|
+
const fileOrRead = FILE_TOOLS.has(input?.tool_name) || READ_TOOLS.has(input?.tool_name);
|
|
443
|
+
const direct = fileOrRead ? null : input?.tool_input?.command;
|
|
444
|
+
// Intrinsically read-only one-program calls cannot meet an older guard rule or write a shell target. Avoid the full
|
|
445
|
+
// shell/environment parse and YAML load on this latency-critical path; compound/substituted/redirection text stays slow.
|
|
446
|
+
if (guard?.role !== 'critic' && typeof direct === 'string' && direct.trim() && !/[;&|(){}<>\n\r`$]/.test(direct) && !/env:/i.test(direct)) {
|
|
447
|
+
const first = direct.trim().split(/\s+/, 1)[0];
|
|
448
|
+
if (intrinsicPolicyRead({ program: programOf(first) })) return 0;
|
|
449
|
+
}
|
|
450
|
+
const decision = await runHookVerdict({ input, env, root, bindings: { guard, seat } });
|
|
451
|
+
if (!decision) return 0;
|
|
452
|
+
const { refusalLines, logRefusal } = await import('./refusals.mjs');
|
|
453
|
+
const { tool, ...verdict } = decision.verdict;
|
|
454
|
+
// Diagnostic delivery cannot change a refusal, including a broken host stderr pipe.
|
|
455
|
+
await writeHookOutput(stderr, `${refusalLines(tool, verdict).join('\n')}\n`).catch(() => false);
|
|
456
|
+
logRefusal({ tool, via: 'pre-tool-use', role: decision.guard?.role ?? seat?.role ?? null, ...verdict, jobId: decision.guard?.jobId ?? null, workflowId: decision.guard?.workflowId ?? null, cwd: decision.cwd }, { root });
|
|
457
|
+
return 2;
|
|
458
|
+
} catch (e) {
|
|
459
|
+
const outcome = enforced ? 'refusing the command' : 'passing the command through';
|
|
460
|
+
await writeHookOutput(stderr, `starci guard: guard error (${hookErrorMessage(e)}); ${outcome}\n`).catch(() => false);
|
|
461
|
+
return enforced ? 2 : 0;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (isMain(import.meta.url)) {
|
|
466
|
+
if (process.connected && process.send) receiveVerdict(hookDecision);
|
|
467
|
+
else process.exit(await main());
|
|
468
|
+
}
|