@starci/hfs 4.7.32 → 4.7.33
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 +9 -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 +54 -14
- 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,353 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// draw-rationale.mjs — owner ruling 2026-09-27 (draw-rationale-evidence): the owner critiques a drawing from
|
|
3
|
+
// EVIDENCE, never from taste. Every interface.draw shape ships, next to its render source, a rationale.json - an
|
|
4
|
+
// array of decisions
|
|
5
|
+
//
|
|
6
|
+
// {id, selector, kind: element|layout|spacing|type|radius|colour|art, decision, value, because, rules[],
|
|
7
|
+
// alternativesRejected[{option, why}], source}
|
|
8
|
+
//
|
|
9
|
+
// and every visible element and region of the render source carries data-why="<id>[ <id>...]" naming its
|
|
10
|
+
// decisions. draw-render.mjs measures the render itself (measureRationale: getComputedStyle over every visible
|
|
11
|
+
// element) and captures an annotated redline render beside each part (<base>.redline.png: spacing brackets with the
|
|
12
|
+
// value and the rule id, DNA labels at component roots). DRAW_RATIONALE_MISSING refuses, per render source:
|
|
13
|
+
// - no rationale file (<source stem>.rationale.json, else rationale.json beside it), or an entry that is malformed
|
|
14
|
+
// (a field missing, an unknown kind, a duplicate id);
|
|
15
|
+
// - a visible element or region without data-why, or a data-why naming no entry; a region whose decision is not a
|
|
16
|
+
// layout decision; no layout decision stating the region order (its value names the region count);
|
|
17
|
+
// - a grid whose layout decision does not state its column count at a viewport;
|
|
18
|
+
// - a distinct gap/padding/inset, radius, font-size, font-weight or line-height the RENDER uses (measured at every
|
|
19
|
+
// viewport) that no decision of the matching kind states in its value (spacing|layout, radius, type);
|
|
20
|
+
// - a colour the render paints (background, text, border) that no colour decision names (hex, or a --token that
|
|
21
|
+
// resolves to it in the render);
|
|
22
|
+
// - a DNA component or a closed variant value the render uses that no decision names;
|
|
23
|
+
// - an art slot (img, picture, data-asset-slot, data-artwork-slot, a background image) whose decision is not art;
|
|
24
|
+
// - a rules[] entry that resolves to nothing: its first token must be a knowledge id (knowledge/**: GAP-5,
|
|
25
|
+
// PADDING-9 case-1, ui.presentation.gap), a DNA component or closed value (dna:Button.variant=secondary), a
|
|
26
|
+
// brand.direction principle/rubric id (direction:P2, rubric:H3), or an owner ruling (owner:<id> of
|
|
27
|
+
// modules/kernel/owner-rulings.yaml, a ui.review.feedback note id, an answer receipt);
|
|
28
|
+
// - an empty `because`, or one that cites no FR, content or user job;
|
|
29
|
+
// - a part with no measured render (re-render it with draw-render) or no redline render beside it.
|
|
30
|
+
//
|
|
31
|
+
// starci work draw-rationale <render.html> [--rationale <file>] [--ui <ui-record-dir>]
|
|
32
|
+
// [--records <draw-render.json>[,<...>]] [--json]
|
|
33
|
+
import fs from 'node:fs';
|
|
34
|
+
import path from 'node:path';
|
|
35
|
+
import { fileURLToPath } from 'node:url';
|
|
36
|
+
import { parseYaml } from '../../../engine/yaml.mjs';
|
|
37
|
+
import { COMPONENT_ATTR, loadDna, walkElements } from './draw-dna.mjs';
|
|
38
|
+
import { findRationaleIssues } from './rationale-findings.mjs';
|
|
39
|
+
import { bareRuleToken, parseRuleRef } from './draw-rationale-ref.mjs';
|
|
40
|
+
import { list } from '../../lib/list.mjs';
|
|
41
|
+
import { isFile } from '../../lib/fs-kind.mjs'; import { isMain } from '../../lib/is-main.mjs';
|
|
42
|
+
|
|
43
|
+
export { drawRedlines, measureRationale } from './draw-rationale-page.mjs';
|
|
44
|
+
|
|
45
|
+
export const DRAW_RATIONALE_MISSING = 'DRAW_RATIONALE_MISSING';
|
|
46
|
+
const RATIONALE_KINDS = Object.freeze(['element', 'layout', 'spacing', 'type', 'radius', 'colour', 'art']);
|
|
47
|
+
export const WHY_ATTR = 'data-why';
|
|
48
|
+
export const REDLINE_ATTR = 'data-draw-redline';
|
|
49
|
+
export const MEASURE_SCHEMA = 'starci/draw-rationale-measure@1';
|
|
50
|
+
const REQUIRED_FIELDS = Object.freeze(['id', 'selector', 'kind', 'decision', 'value', 'because', 'rules', 'alternativesRejected', 'source']);
|
|
51
|
+
/** Components too small to label in the redline (they are named by their region's label). */
|
|
52
|
+
export const REDLINE_LEAF_COMPONENTS = Object.freeze(['Text', 'Heading', 'Icon', 'Label', 'Description', 'Kbd', 'Badge', 'Chip', 'IconTile', 'TagGroup', 'Tag', 'StateMark', 'MediaFrame', 'Avatar']);
|
|
53
|
+
|
|
54
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
|
|
55
|
+
const KNOWLEDGE = path.join(ROOT, 'knowledge');
|
|
56
|
+
const OWNER_RULINGS = path.join(ROOT, 'modules', 'kernel', 'owner-rulings.yaml');
|
|
57
|
+
const readJson = (f) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch { return undefined; } };
|
|
58
|
+
const readYamlOr = (f) => { try { return parseYaml(fs.readFileSync(f, 'utf8')); } catch { return null; } };
|
|
59
|
+
const str = (v) => { if (v == null) { return ''; } if (typeof v === 'string') { return v; } if (typeof v === 'number') { return String(v); } return JSON.stringify(v); };
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------------------------------------
|
|
62
|
+
// The rationale file
|
|
63
|
+
// ---------------------------------------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
/** The rationale file of a render source: <stem>.rationale.json beside it, else rationale.json in its directory. */
|
|
66
|
+
export function rationaleFileOf(htmlFile) {
|
|
67
|
+
if (!htmlFile) return null;
|
|
68
|
+
const own = String(htmlFile).replace(/\.html?$/i, '.rationale.json');
|
|
69
|
+
if (isFile(own)) return own;
|
|
70
|
+
const shared = path.join(path.dirname(htmlFile), 'rationale.json');
|
|
71
|
+
return isFile(shared) ? shared : null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Read and validate a rationale file: {entries, errors:[string]}. A missing or unparseable file is one error. */
|
|
75
|
+
export function loadRationale(file) {
|
|
76
|
+
if (!file || !isFile(file)) return { entries: [], errors: ['no rationale.json beside the render source'] };
|
|
77
|
+
const doc = readJson(file);
|
|
78
|
+
if (doc === undefined) return { entries: [], errors: [`${path.basename(file)} is not JSON`] };
|
|
79
|
+
const entries = Array.isArray(doc) ? doc : null;
|
|
80
|
+
if (!entries) return { entries: [], errors: [`${path.basename(file)} is not an array of decisions`] };
|
|
81
|
+
if (!entries.length) return { entries: [], errors: [`${path.basename(file)} holds no decision`] };
|
|
82
|
+
const errors = [];
|
|
83
|
+
const seen = new Set();
|
|
84
|
+
entries.forEach((e, i) => {
|
|
85
|
+
const at = `decision ${e?.id ?? '#' + (i + 1)}`;
|
|
86
|
+
if (!e || typeof e !== 'object' || Array.isArray(e)) { errors.push(`#${i + 1} is not an object`); return; }
|
|
87
|
+
const missing = REQUIRED_FIELDS.filter((k) => e[k] == null || (typeof e[k] === 'string' && !e[k].trim()));
|
|
88
|
+
if (missing.length) errors.push(`${at} lacks ${missing.join(', ')}`);
|
|
89
|
+
if (e.id != null) { if (seen.has(String(e.id))) { errors.push(`${at}: the id is used twice`); } seen.add(String(e.id)); }
|
|
90
|
+
if (e.kind != null && !RATIONALE_KINDS.includes(e.kind)) errors.push(`${at}: kind "${e.kind}" is none of ${RATIONALE_KINDS.join('|')}`);
|
|
91
|
+
if (e.rules != null && (!Array.isArray(e.rules) || !e.rules.length)) errors.push(`${at}: rules[] must cite at least one rule id`);
|
|
92
|
+
if (e.alternativesRejected != null && (!Array.isArray(e.alternativesRejected) || e.alternativesRejected.some((a) => !a || typeof a !== 'object' || !str(a.option).trim() || !str(a.why).trim()))) {
|
|
93
|
+
errors.push(`${at}: alternativesRejected is [{option, why}]`);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return { entries: entries.filter((e) => e && typeof e === 'object' && !Array.isArray(e)), errors };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The labels the redline shows per decision: {id: {kind, value, rule}} - rule is the first rule's id token. */
|
|
100
|
+
export function redlineLabelsOf(entries) {
|
|
101
|
+
const out = {};
|
|
102
|
+
for (const e of list(entries)) {
|
|
103
|
+
if (e?.id == null) continue;
|
|
104
|
+
out[String(e.id)] = { kind: e.kind ?? null, value: str(e.value).slice(0, 40), rule: ruleTokenOf(list(e.rules)[0]) };
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ---------------------------------------------------------------------------------------------------------
|
|
110
|
+
// Rule id resolution
|
|
111
|
+
// ---------------------------------------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
const NAMESPACES = ['knowledge', 'dna', 'grammar', 'direction', 'rubric', 'owner'];
|
|
114
|
+
|
|
115
|
+
/** The id token of a rules[] entry (namespace dropped), for labels. */
|
|
116
|
+
function ruleTokenOf(ref) {
|
|
117
|
+
const m = parseRuleRef(str(ref));
|
|
118
|
+
if (!m) return '';
|
|
119
|
+
return `${m[2]}${m[3] ? ' ' + m[3] : ''}`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let knowledgeCache = null;
|
|
123
|
+
/** Every id knowledge/** declares: {ids:Set, cases:Map(ruleId -> Set(case ids))}. */
|
|
124
|
+
function addKnowledgeRecord(node, ids, cases) {
|
|
125
|
+
if (typeof node.id !== 'string' || !node.id.trim()) return;
|
|
126
|
+
const id = node.id.trim();
|
|
127
|
+
if (!/^case-\d+$/i.test(id)) ids.add(id);
|
|
128
|
+
if (!Array.isArray(node.cases)) return;
|
|
129
|
+
const set = cases.get(id) ?? new Set();
|
|
130
|
+
for (const item of node.cases) if (typeof item?.id === 'string') set.add(item.id.trim());
|
|
131
|
+
cases.set(id, set);
|
|
132
|
+
}
|
|
133
|
+
function visitKnowledgeNode(node, ids, cases) {
|
|
134
|
+
if (Array.isArray(node)) {
|
|
135
|
+
for (const item of node) visitKnowledgeNode(item, ids, cases);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (!node || typeof node !== 'object') return;
|
|
139
|
+
addKnowledgeRecord(node, ids, cases);
|
|
140
|
+
for (const value of Object.values(node)) if (value && typeof value === 'object') visitKnowledgeNode(value, ids, cases);
|
|
141
|
+
}
|
|
142
|
+
function knowledgeIndex(root = KNOWLEDGE) {
|
|
143
|
+
if (knowledgeCache?.root === root) return knowledgeCache;
|
|
144
|
+
const ids = new Set(), cases = new Map();
|
|
145
|
+
const walk = (dir) => {
|
|
146
|
+
let names = [];
|
|
147
|
+
try { names = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
148
|
+
for (const d of names) {
|
|
149
|
+
if (d.name.startsWith('.')) continue;
|
|
150
|
+
const p = path.join(dir, d.name);
|
|
151
|
+
if (d.isDirectory()) walk(p);
|
|
152
|
+
else if (/\.ya?ml$/i.test(d.name)) visitKnowledgeNode(readYamlOr(p), ids, cases);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
walk(root);
|
|
156
|
+
knowledgeCache = { root, ids, cases };
|
|
157
|
+
return knowledgeCache;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const idsUnder = (node, out = new Set()) => {
|
|
161
|
+
if (Array.isArray(node)) { for (const n of node) { idsUnder(n, out); } return out; }
|
|
162
|
+
if (!node || typeof node !== 'object') return out;
|
|
163
|
+
if (typeof node.id === 'string' && node.id.trim()) out.add(node.id.trim());
|
|
164
|
+
for (const v of Object.values(node)) if (v && typeof v === 'object') idsUnder(v, out);
|
|
165
|
+
return out;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/** The product's brand.direction: {all:Set, rubric:Set} of its ids. */
|
|
169
|
+
function directionIds(workRoot) {
|
|
170
|
+
const doc = workRoot ? readYamlOr(path.join(workRoot, 'brand', 'index.yaml')) : null;
|
|
171
|
+
const direction = doc?.brand?.direction ?? doc?.direction ?? null;
|
|
172
|
+
return { all: idsUnder(direction), rubric: new Set(list(direction?.rubric?.checks).map((c) => str(c?.id).trim()).filter(Boolean)) };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Current owner rulings, product feedback notes and actual receipts. */
|
|
176
|
+
function ownerIds({ record = null, workRoot = null, rulingsFile = OWNER_RULINGS } = {}) {
|
|
177
|
+
const ids = new Set();
|
|
178
|
+
for (const r of list(readYamlOr(rulingsFile)?.rulings)) if (r?.id) ids.add(str(r.id).trim());
|
|
179
|
+
const review = record?.ui?.review ?? {};
|
|
180
|
+
for (const n of idsUnder(review.feedback)) ids.add(n);
|
|
181
|
+
for (const r of list(review.feedback?.rounds)) if (r?.receipt) ids.add(str(r.receipt));
|
|
182
|
+
if (review.owner?.receipt) ids.add(str(review.owner.receipt));
|
|
183
|
+
if (review.owner?.dispatchId) ids.add(str(review.owner.dispatchId));
|
|
184
|
+
const doc = workRoot ? readYamlOr(path.join(workRoot, 'brand', 'index.yaml')) : null;
|
|
185
|
+
for (const l of list((doc?.brand?.direction ?? doc?.direction)?.learned)) if (l?.id) ids.add(str(l.id).trim());
|
|
186
|
+
return ids;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const kebab = (s) => String(s).replace(/([a-z0-9])([A-Z])/g, '$1-$2').replace(/([A-Z])([A-Z][a-z])/g, '$1-$2').toLowerCase();
|
|
190
|
+
|
|
191
|
+
/** Whether `token` names a DNA component, one of its parts, props or closed values (Button, Button.variant=secondary). */
|
|
192
|
+
function dnaResolves(dna, token) {
|
|
193
|
+
if (!dna) return false;
|
|
194
|
+
const [head, rest] = String(token).split(/\.(.+)/);
|
|
195
|
+
const spec = dna.components.get(head);
|
|
196
|
+
if (!spec) return false;
|
|
197
|
+
if (!rest) return true;
|
|
198
|
+
const [prop, value] = rest.split('=');
|
|
199
|
+
if (spec.parts.has(prop) || spec.parts.has(kebab(prop))) return value == null;
|
|
200
|
+
const closed = spec.closed.get(prop);
|
|
201
|
+
if (!closed) return false;
|
|
202
|
+
if (value == null) return true;
|
|
203
|
+
return closed.values ? closed.values.includes(value) : true;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* A resolver of rules[] entries: (ref) -> {ok, via, id, why?}. Context: {workRoot, record, dna, knowledge,
|
|
208
|
+
* rulingsFile, repoRoot}.
|
|
209
|
+
*/
|
|
210
|
+
export function ruleResolver({ workRoot = null, record = null, dna = loadDna(), knowledge = knowledgeIndex(), rulingsFile = OWNER_RULINGS, repoRoot = null } = {}) {
|
|
211
|
+
const direction = directionIds(workRoot);
|
|
212
|
+
const owner = ownerIds({ record, workRoot, rulingsFile });
|
|
213
|
+
const receiptOk = (token) => {
|
|
214
|
+
for (const base of [repoRoot, workRoot ? path.dirname(workRoot) : null, workRoot].filter(Boolean)) {
|
|
215
|
+
const f = path.resolve(base, token);
|
|
216
|
+
if (isFile(f) && readJson(f)?.schema === 'starci/ask-answer@1') return true;
|
|
217
|
+
}
|
|
218
|
+
return false;
|
|
219
|
+
};
|
|
220
|
+
const inKnowledge = (id, kase) => {
|
|
221
|
+
if (!knowledge.ids.has(id)) return { ok: false, why: `${id} is no id in knowledge/` };
|
|
222
|
+
if (kase && !(knowledge.cases.get(id)?.has(kase))) return { ok: false, why: `${id} has no ${kase}` };
|
|
223
|
+
return { ok: true };
|
|
224
|
+
};
|
|
225
|
+
const resolveNamespace = (namespace, token, kase) => {
|
|
226
|
+
if (namespace === 'knowledge') {
|
|
227
|
+
const result = inKnowledge(token, kase);
|
|
228
|
+
return result.ok ? { ok: true, via: 'knowledge', id: token } : { via: 'knowledge', why: result.why };
|
|
229
|
+
}
|
|
230
|
+
if (namespace === 'dna' && dnaResolves(dna, token)) return { ok: true, via: 'dna', id: token };
|
|
231
|
+
if (namespace === 'direction' && direction.all.has(token)) return { ok: true, via: 'direction', id: token };
|
|
232
|
+
if (namespace === 'rubric' && direction.rubric.has(token)) return { ok: true, via: 'rubric', id: token };
|
|
233
|
+
if (namespace === 'owner' && (owner.has(token) || receiptOk(token))) return { ok: true, via: 'owner', id: token };
|
|
234
|
+
return null;
|
|
235
|
+
};
|
|
236
|
+
return (ref) => {
|
|
237
|
+
const text = str(ref);
|
|
238
|
+
const m = parseRuleRef(text);
|
|
239
|
+
if (!m?.[2]) return { ok: false, id: text, why: 'empty rule reference' };
|
|
240
|
+
const ns = m[1] ? m[1].toLowerCase() : null;
|
|
241
|
+
const token = bareRuleToken(m[2]);
|
|
242
|
+
const kase = m[3] ?? null;
|
|
243
|
+
if (ns && !NAMESPACES.includes(ns)) return { ok: false, id: token, why: `unknown namespace ${ns}: (one of ${NAMESPACES.join(', ')})` };
|
|
244
|
+
const tries = ns ? [ns === 'grammar' ? 'dna' : ns] : ['knowledge', 'dna', 'direction', 'owner'];
|
|
245
|
+
let why = null;
|
|
246
|
+
for (const t of tries) {
|
|
247
|
+
const result = resolveNamespace(t, token, kase);
|
|
248
|
+
if (result?.ok) return result;
|
|
249
|
+
if (result?.via === 'knowledge') why ??= result.why;
|
|
250
|
+
}
|
|
251
|
+
return { ok: false, id: token, why: ns ? `${ns}:${token} resolves to nothing` : (why ?? `${token} resolves to no knowledge id, DNA name, brand.direction id or owner ruling`) };
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ---------------------------------------------------------------------------------------------------------
|
|
256
|
+
// Numbers stated in a decision
|
|
257
|
+
// ---------------------------------------------------------------------------------------------------------
|
|
258
|
+
|
|
259
|
+
/** The numbers a decision states, in px where a unit says so (1.5rem -> 24); pill/full/circle as words. */
|
|
260
|
+
export function statedValues(text) {
|
|
261
|
+
const s = str(text);
|
|
262
|
+
const nums = new Set();
|
|
263
|
+
for (const m of s.matchAll(/(\d+(?:\.\d+)?)\s*(rem|em|px|%)?/gi)) {
|
|
264
|
+
const n = Number(m[1]);
|
|
265
|
+
if (!Number.isFinite(n)) continue;
|
|
266
|
+
const unit = (m[2] ?? '').toLowerCase();
|
|
267
|
+
if (unit === 'rem' || unit === 'em') nums.add(round(n * 16));
|
|
268
|
+
else nums.add(round(n));
|
|
269
|
+
}
|
|
270
|
+
return { nums, pill: /\b(pill|full|9999|rounded-full|capsule)\b/i.test(s), circle: /\b(circle|50%)\b/i.test(s) };
|
|
271
|
+
}
|
|
272
|
+
const round = (n) => Math.round(Number(n) * 100) / 100;
|
|
273
|
+
|
|
274
|
+
const statesValue = (entry, v) => {
|
|
275
|
+
const st = statedValues(`${str(entry.value)}`);
|
|
276
|
+
if (typeof v === 'string') {
|
|
277
|
+
if (v === 'pill') return st.pill;
|
|
278
|
+
if (v === 'circle') return st.circle;
|
|
279
|
+
return str(entry.value).toLowerCase().includes(v.toLowerCase());
|
|
280
|
+
}
|
|
281
|
+
return [...st.nums].some((n) => Math.abs(n - v) < 0.26);
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
/** The id-and-reference test of `because`: a record ref, an FR/NFR/BR id, the content or a user job. */
|
|
285
|
+
export function becauseCites(because, refs = []) {
|
|
286
|
+
const s = str(because);
|
|
287
|
+
if (!s.trim()) return false;
|
|
288
|
+
if (refs.some((r) => r && s.includes(r))) return true;
|
|
289
|
+
return [
|
|
290
|
+
/\b(?:N?FR|BR|UJ|JTBD|US)[-.]?[a-z0-9]*\d/i,
|
|
291
|
+
/\bfr\.[a-z0-9][\w.-]*/i,
|
|
292
|
+
/\b\d{2}-CONTENT\b/i,
|
|
293
|
+
/\bcontent\b/i,
|
|
294
|
+
/\buser(?:'s)? job\b/i,
|
|
295
|
+
/\bjob[- ]to[- ]be[- ]done\b/i,
|
|
296
|
+
/\bjob:/i,
|
|
297
|
+
].some((pattern) => pattern.test(s));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** The region container of a render: the PageContainer root, else <main>, else <body>. */
|
|
301
|
+
export function regionContainerOf(tree) {
|
|
302
|
+
const all = walkElements(tree);
|
|
303
|
+
return all.find((e) => str(e.attrs[COMPONENT_ATTR]).trim() === 'PageContainer') ?? all.find((e) => e.tag === 'main') ?? all.find((e) => e.tag === 'body') ?? tree;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* DRAW_RATIONALE_MISSING findings of one render source. `html` its text; `entries`/`errors` from loadRationale;
|
|
308
|
+
* `measures` the draw-render measurements (measureRationale) of every viewport it was rendered at; `resolve` a
|
|
309
|
+
* ruleResolver; `record` the ui record (its refs); `redlines` [{viewport, ok}] when the caller checks the redline
|
|
310
|
+
* renders. Returns [{code, kind, detail, count, examples}].
|
|
311
|
+
*/
|
|
312
|
+
export function rationaleFindings({ html, entries = [], errors = [], measures = [], resolve = ruleResolver(), record = null, dna = loadDna(), label = 'the render', redlines = null } = {}) {
|
|
313
|
+
return findRationaleIssues({ html, entries, errors, measures, resolve, record, dna, label, redlines }, {
|
|
314
|
+
str, statesValue, round, statedValues, becauseCites, kebab, regionContainerOf,
|
|
315
|
+
code: DRAW_RATIONALE_MISSING, measureSchema: MEASURE_SCHEMA, whyAttr: WHY_ATTR, redlineAttr: REDLINE_ATTR,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
/** The measures of a render source's parts: the rationale block of each starci/draw-render@1 record given. */
|
|
319
|
+
export const measuresOf = (records) => list(records).map((r) => r?.rationale ?? null).filter(Boolean);
|
|
320
|
+
|
|
321
|
+
/** A one-line summary of a rationale file for the owner's ask: {decisions, byKind}. */
|
|
322
|
+
export function rationaleSummary(file) {
|
|
323
|
+
const { entries } = loadRationale(file);
|
|
324
|
+
const byKind = {};
|
|
325
|
+
for (const e of entries) byKind[e.kind] = (byKind[e.kind] ?? 0) + 1;
|
|
326
|
+
return { decisions: entries.length, byKind };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ---------------------------------------------------------------------------------------------------------
|
|
330
|
+
// CLI
|
|
331
|
+
// ---------------------------------------------------------------------------------------------------------
|
|
332
|
+
|
|
333
|
+
async function main(argv) {
|
|
334
|
+
const file = argv.find((a) => !a.startsWith('--') && /\.html?$/i.test(a));
|
|
335
|
+
const at = (n) => { const i = argv.indexOf(n); return i >= 0 ? argv[i + 1] : null; };
|
|
336
|
+
if (!file) { process.stderr.write('use: starci work draw-rationale <render.html> [--rationale <file>] [--ui <ui-record-dir>] [--records <draw-render.json>[,...]] [--json]\n'); return 2; }
|
|
337
|
+
const html = path.resolve(file);
|
|
338
|
+
const rationale = at('--rationale') ? path.resolve(at('--rationale')) : rationaleFileOf(html);
|
|
339
|
+
const { entries, errors } = loadRationale(rationale);
|
|
340
|
+
const uiDir = at('--ui') ? path.resolve(at('--ui')) : null;
|
|
341
|
+
const record = uiDir ? readYamlOr(path.join(uiDir, 'index.yaml')) : null;
|
|
342
|
+
let workRoot = null;
|
|
343
|
+
for (let d = uiDir ?? path.dirname(html); ; d = path.dirname(d)) { if (path.basename(d) === '.starciwork') { workRoot = d; break; } if (path.dirname(d) === d) break; }
|
|
344
|
+
const records = (at('--records') ? at('--records').split(',') : []).map((f) => readJson(path.resolve(f)));
|
|
345
|
+
const findings = rationaleFindings({ html: fs.readFileSync(html, 'utf8'), entries, errors, measures: measuresOf(records), resolve: ruleResolver({ workRoot, record, repoRoot: process.cwd() }), record, label: path.basename(html) });
|
|
346
|
+
if (argv.includes('--json')) process.stdout.write(`${JSON.stringify({ ok: !findings.length, rationale, decisions: entries.length, findings }, null, 2)}\n`);
|
|
347
|
+
else process.stdout.write(`${findings.length ? 'REFUSED' : 'ok'}: ${entries.length} decision(s), ${findings.length} finding group(s)\n${findings.map((f) => ' [' + f.code + '] ' + f.detail).join('\n')}\n`);
|
|
348
|
+
return findings.length ? 1 : 0;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (isMain(import.meta.url)) {
|
|
352
|
+
process.exitCode = await main(process.argv.slice(2));
|
|
353
|
+
}
|