@starci/skills 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INDEX.md +74 -0
- package/INDEX.vi.md +75 -0
- package/README.md +44 -0
- package/README.vi.md +43 -0
- package/SKILL.md +135 -0
- package/SKILL.vi.md +128 -0
- package/alias/INDEX.md +104 -0
- package/alias/INDEX.vi.md +104 -0
- package/alias/alias.json +297 -0
- package/bin/starci-skills.mjs +219 -0
- package/knowledge/INDEX.md +22 -0
- package/knowledge/INDEX.vi.md +22 -0
- package/knowledge/grammars/starci/DNA.md +146 -0
- package/knowledge/grammars/starci/DNA.vi.md +146 -0
- package/knowledge/grammars/starci/INDEX.md +25 -0
- package/knowledge/grammars/starci/INDEX.vi.md +25 -0
- package/knowledge/grammars/starci/family.md +50 -0
- package/knowledge/grammars/starci/family.vi.md +50 -0
- package/knowledge/grammars/starci/idioms.md +120 -0
- package/knowledge/grammars/starci/idioms.vi.md +121 -0
- package/knowledge/grammars/starci/playbook.md +36 -0
- package/knowledge/grammars/starci/playbook.vi.md +35 -0
- package/knowledge/patterns/be/INDEX.md +29 -0
- package/knowledge/patterns/be/INDEX.vi.md +29 -0
- package/knowledge/patterns/be/comment.md +80 -0
- package/knowledge/patterns/be/comment.vi.md +80 -0
- package/knowledge/patterns/be/error.md +86 -0
- package/knowledge/patterns/be/error.vi.md +86 -0
- package/knowledge/patterns/be/folder.md +87 -0
- package/knowledge/patterns/be/folder.vi.md +86 -0
- package/knowledge/patterns/be/function.md +80 -0
- package/knowledge/patterns/be/function.vi.md +80 -0
- package/knowledge/patterns/be/imports.md +79 -0
- package/knowledge/patterns/be/imports.vi.md +79 -0
- package/knowledge/patterns/be/naming.md +87 -0
- package/knowledge/patterns/be/naming.vi.md +87 -0
- package/knowledge/patterns/be/test.md +79 -0
- package/knowledge/patterns/be/test.vi.md +79 -0
- package/knowledge/patterns/be/typing.md +73 -0
- package/knowledge/patterns/be/typing.vi.md +73 -0
- package/knowledge/patterns/fe/INDEX.md +29 -0
- package/knowledge/patterns/fe/INDEX.vi.md +29 -0
- package/knowledge/patterns/fe/comment.md +70 -0
- package/knowledge/patterns/fe/comment.vi.md +70 -0
- package/knowledge/patterns/fe/error.md +61 -0
- package/knowledge/patterns/fe/error.vi.md +61 -0
- package/knowledge/patterns/fe/folder.md +98 -0
- package/knowledge/patterns/fe/folder.vi.md +95 -0
- package/knowledge/patterns/fe/function.md +70 -0
- package/knowledge/patterns/fe/function.vi.md +70 -0
- package/knowledge/patterns/fe/imports.md +87 -0
- package/knowledge/patterns/fe/imports.vi.md +87 -0
- package/knowledge/patterns/fe/naming.md +77 -0
- package/knowledge/patterns/fe/naming.vi.md +77 -0
- package/knowledge/patterns/fe/test.md +73 -0
- package/knowledge/patterns/fe/test.vi.md +73 -0
- package/knowledge/patterns/fe/typing.md +67 -0
- package/knowledge/patterns/fe/typing.vi.md +67 -0
- package/knowledge/ui/INDEX.md +101 -0
- package/knowledge/ui/INDEX.vi.md +101 -0
- package/knowledge/ui/composition/INDEX.md +60 -0
- package/knowledge/ui/composition/INDEX.vi.md +63 -0
- package/knowledge/ui/composition/accent.md +73 -0
- package/knowledge/ui/composition/accent.vi.md +74 -0
- package/knowledge/ui/composition/action.md +55 -0
- package/knowledge/ui/composition/action.vi.md +56 -0
- package/knowledge/ui/composition/coverage.md +37 -0
- package/knowledge/ui/composition/coverage.vi.md +37 -0
- package/knowledge/ui/composition/cta.md +79 -0
- package/knowledge/ui/composition/cta.vi.md +79 -0
- package/knowledge/ui/composition/feedback.md +55 -0
- package/knowledge/ui/composition/feedback.vi.md +56 -0
- package/knowledge/ui/composition/hierarchy.md +83 -0
- package/knowledge/ui/composition/hierarchy.vi.md +82 -0
- package/knowledge/ui/composition/layout.md +91 -0
- package/knowledge/ui/composition/layout.vi.md +93 -0
- package/knowledge/ui/composition/responsive.md +67 -0
- package/knowledge/ui/composition/responsive.vi.md +67 -0
- package/knowledge/ui/composition/state.md +105 -0
- package/knowledge/ui/composition/state.vi.md +106 -0
- package/knowledge/ui/presentation/INDEX.md +94 -0
- package/knowledge/ui/presentation/INDEX.vi.md +69 -0
- package/knowledge/ui/presentation/boundary.md +170 -0
- package/knowledge/ui/presentation/boundary.vi.md +169 -0
- package/knowledge/ui/presentation/font.md +155 -0
- package/knowledge/ui/presentation/font.vi.md +156 -0
- package/knowledge/ui/presentation/gap.md +181 -0
- package/knowledge/ui/presentation/gap.vi.md +181 -0
- package/knowledge/ui/presentation/margin.md +168 -0
- package/knowledge/ui/presentation/margin.vi.md +171 -0
- package/knowledge/ui/presentation/measure.md +174 -0
- package/knowledge/ui/presentation/measure.vi.md +178 -0
- package/knowledge/ui/presentation/overflow.md +142 -0
- package/knowledge/ui/presentation/overflow.vi.md +142 -0
- package/knowledge/ui/presentation/padding.md +293 -0
- package/knowledge/ui/presentation/padding.vi.md +292 -0
- package/knowledge/ui/presentation/surface.md +166 -0
- package/knowledge/ui/presentation/surface.vi.md +166 -0
- package/knowledge/ui/presentation/text-flow.md +123 -0
- package/knowledge/ui/presentation/text-flow.vi.md +123 -0
- package/knowledge/ui/presentation/tone.md +114 -0
- package/knowledge/ui/presentation/tone.vi.md +113 -0
- package/knowledge/ui/proof/INDEX.md +55 -0
- package/knowledge/ui/proof/INDEX.vi.md +55 -0
- package/knowledge/ui/proof/accessibility.md +67 -0
- package/knowledge/ui/proof/accessibility.vi.md +68 -0
- package/knowledge/ui/proof/contrast.md +58 -0
- package/knowledge/ui/proof/contrast.vi.md +57 -0
- package/knowledge/ui/proof/focus.md +76 -0
- package/knowledge/ui/proof/focus.vi.md +76 -0
- package/knowledge/ui/proof/motion.md +63 -0
- package/knowledge/ui/proof/motion.vi.md +64 -0
- package/knowledge/ui/proof/render-truth.md +63 -0
- package/knowledge/ui/proof/render-truth.vi.md +63 -0
- package/operators/INDEX.md +199 -0
- package/operators/INDEX.vi.md +199 -0
- package/operators/architecture-decide/errors.json +115 -0
- package/operators/architecture-decide/operator.json +20 -0
- package/operators/architecture-decide/operator.md +133 -0
- package/operators/architecture-decide/operator.vi.md +130 -0
- package/operators/architecture-decide/self-test.mjs +221 -0
- package/operators/architecture-decide/validate.mjs +112 -0
- package/operators/backend-source-apply/errors.json +66 -0
- package/operators/backend-source-apply/operator.json +20 -0
- package/operators/backend-source-apply/operator.md +189 -0
- package/operators/backend-source-apply/operator.vi.md +185 -0
- package/operators/backend-source-apply/self-test.mjs +257 -0
- package/operators/backend-source-apply/validate.mjs +240 -0
- package/operators/business-decide/errors.json +78 -0
- package/operators/business-decide/operator.json +20 -0
- package/operators/business-decide/operator.md +176 -0
- package/operators/business-decide/operator.vi.md +174 -0
- package/operators/business-decide/self-test.mjs +225 -0
- package/operators/business-decide/validate.mjs +277 -0
- package/operators/content-generate/errors.json +106 -0
- package/operators/content-generate/operator.json +21 -0
- package/operators/content-generate/operator.md +155 -0
- package/operators/content-generate/operator.vi.md +155 -0
- package/operators/content-generate/self-test.mjs +288 -0
- package/operators/content-generate/validate.mjs +196 -0
- package/operators/errors.json +178 -0
- package/operators/frontend-direction-decide/errors.json +135 -0
- package/operators/frontend-direction-decide/operator.json +21 -0
- package/operators/frontend-direction-decide/operator.md +167 -0
- package/operators/frontend-direction-decide/operator.vi.md +167 -0
- package/operators/frontend-direction-decide/self-test.mjs +226 -0
- package/operators/frontend-direction-decide/validate.mjs +146 -0
- package/operators/frontend-presentation-resolve/errors.json +42 -0
- package/operators/frontend-presentation-resolve/operator.json +19 -0
- package/operators/frontend-presentation-resolve/operator.md +142 -0
- package/operators/frontend-presentation-resolve/operator.vi.md +140 -0
- package/operators/frontend-presentation-resolve/self-test.mjs +163 -0
- package/operators/frontend-presentation-resolve/validate.mjs +143 -0
- package/operators/frontend-source-apply/errors.json +30 -0
- package/operators/frontend-source-apply/operator.json +20 -0
- package/operators/frontend-source-apply/operator.md +127 -0
- package/operators/frontend-source-apply/operator.vi.md +126 -0
- package/operators/frontend-source-apply/self-test.mjs +214 -0
- package/operators/frontend-source-apply/validate.mjs +133 -0
- package/operators/frontend-surface-audit/errors.json +5 -0
- package/operators/frontend-surface-audit/operator.json +22 -0
- package/operators/frontend-surface-audit/operator.md +121 -0
- package/operators/frontend-surface-audit/operator.vi.md +121 -0
- package/operators/frontend-surface-audit/self-test.mjs +185 -0
- package/operators/frontend-surface-audit/validate.mjs +127 -0
- package/operators/git-publish/errors.json +54 -0
- package/operators/git-publish/operator.json +20 -0
- package/operators/git-publish/operator.md +180 -0
- package/operators/git-publish/operator.vi.md +178 -0
- package/operators/git-publish/self-test.mjs +177 -0
- package/operators/git-publish/validate.mjs +106 -0
- package/operators/platform-operate/errors.json +90 -0
- package/operators/platform-operate/operator.json +22 -0
- package/operators/platform-operate/operator.md +162 -0
- package/operators/platform-operate/operator.vi.md +160 -0
- package/operators/platform-operate/self-test.mjs +202 -0
- package/operators/platform-operate/validate.mjs +193 -0
- package/operators/quality-verify/errors.json +54 -0
- package/operators/quality-verify/operator.json +20 -0
- package/operators/quality-verify/operator.md +185 -0
- package/operators/quality-verify/operator.vi.md +177 -0
- package/operators/quality-verify/self-test.mjs +210 -0
- package/operators/quality-verify/validate.mjs +197 -0
- package/operators/release-deploy/errors.json +158 -0
- package/operators/release-deploy/operator.json +23 -0
- package/operators/release-deploy/operator.md +183 -0
- package/operators/release-deploy/operator.vi.md +181 -0
- package/operators/release-deploy/self-test.mjs +235 -0
- package/operators/release-deploy/validate.mjs +129 -0
- package/operators/uat-verify/errors.json +78 -0
- package/operators/uat-verify/operator.json +25 -0
- package/operators/uat-verify/operator.md +162 -0
- package/operators/uat-verify/operator.vi.md +161 -0
- package/operators/uat-verify/self-test.mjs +270 -0
- package/operators/uat-verify/validate.mjs +202 -0
- package/operators/workspace-bind/errors.json +90 -0
- package/operators/workspace-bind/operator.json +21 -0
- package/operators/workspace-bind/operator.md +148 -0
- package/operators/workspace-bind/operator.vi.md +148 -0
- package/operators/workspace-bind/self-test.mjs +205 -0
- package/operators/workspace-bind/validate.mjs +152 -0
- package/package.json +50 -0
- package/readiness/initialization/workspaces/commit-policy.json +91 -0
- package/readiness/initialization/workspaces/config.schema.json +35 -0
- package/readiness/initialization/workspaces/device-state.schema.json +54 -0
- package/readiness/initialization/workspaces/local-route.schema.json +206 -0
- package/readiness/initialization/workspaces/portable-route.schema.json +200 -0
- package/resources/INDEX.md +96 -0
- package/resources/INDEX.vi.md +99 -0
- package/resources/agents/profiles/claude.json +131 -0
- package/resources/agents/profiles/openai.json +131 -0
- package/resources/orchestrator.json +71 -0
- package/resources/tools.json +85 -0
- package/routing.json +272 -0
- package/scripts/alias-registry.mjs +31 -0
- package/scripts/device-state.mjs +497 -0
- package/scripts/device-state.spec.mjs +18 -0
- package/scripts/errors-registry.mjs +68 -0
- package/scripts/generate-alias-doc.mjs +66 -0
- package/scripts/generate-grammar-dna.mjs +387 -0
- package/scripts/generate-operators-index.mjs +95 -0
- package/scripts/generate-presentation-owned.mjs +681 -0
- package/scripts/install-cli.spec.mjs +74 -0
- package/scripts/json-schema.mjs +94 -0
- package/scripts/operator-md.mjs +96 -0
- package/scripts/run-operator-self-tests.mjs +36 -0
- package/scripts/validate-alias.mjs +165 -0
- package/scripts/validate-defaults.mjs +72 -0
- package/scripts/validate-knowledge-citations.mjs +90 -0
- package/scripts/validate-operator.mjs +125 -0
- package/scripts/validate-request.mjs +80 -0
- package/scripts/validate-resources.mjs +117 -0
- package/scripts/validate-response.mjs +141 -0
- package/scripts/validate-routing.mjs +91 -0
- package/scripts/validate-step.mjs +50 -0
- package/scripts/validate-templates.mjs +226 -0
- package/scripts/validate-templates.spec.mjs +144 -0
- package/scripts/validate-workflows.mjs +106 -0
- package/scripts/workspace-portable.mjs +389 -0
- package/scripts/workspace-portable.spec.mjs +246 -0
- package/templates/README.md +47 -0
- package/templates/README.vi.md +45 -0
- package/templates/changes.example.md +27 -0
- package/templates/grammars.template.md +34 -0
- package/templates/kinds/architecture-decision.contract.json +14 -0
- package/templates/kinds/architecture-decision.skeleton.md +52 -0
- package/templates/kinds/backend-source-application.contract.json +10 -0
- package/templates/kinds/backend-source-application.skeleton.md +34 -0
- package/templates/kinds/business-promise-authority.contract.json +13 -0
- package/templates/kinds/business-promise-authority.skeleton.md +67 -0
- package/templates/kinds/capture.schema.json +33 -0
- package/templates/kinds/changes.contract.json +10 -0
- package/templates/kinds/changes.skeleton.md +26 -0
- package/templates/kinds/checks.schema.json +44 -0
- package/templates/kinds/claims.schema.json +165 -0
- package/templates/kinds/conformance.schema.json +15 -0
- package/templates/kinds/content-brief.contract.json +12 -0
- package/templates/kinds/content-brief.skeleton.md +45 -0
- package/templates/kinds/content-generation-receipt.contract.json +11 -0
- package/templates/kinds/content-generation-receipt.skeleton.md +40 -0
- package/templates/kinds/content-review.contract.json +11 -0
- package/templates/kinds/content-review.skeleton.md +45 -0
- package/templates/kinds/contract.schema.json +28 -0
- package/templates/kinds/coverage-matrix.schema.json +86 -0
- package/templates/kinds/coverage.schema.json +62 -0
- package/templates/kinds/current-state.schema.json +40 -0
- package/templates/kinds/delta.schema.json +95 -0
- package/templates/kinds/e2e.schema.json +63 -0
- package/templates/kinds/frontend-direction-decision.contract.json +93 -0
- package/templates/kinds/frontend-direction-decision.skeleton.md +68 -0
- package/templates/kinds/frontend-presentation-resolution.contract.json +11 -0
- package/templates/kinds/frontend-presentation-resolution.skeleton.md +32 -0
- package/templates/kinds/frontend-source-application.contract.json +10 -0
- package/templates/kinds/frontend-source-application.skeleton.md +30 -0
- package/templates/kinds/frontend-surface-audit.contract.json +52 -0
- package/templates/kinds/frontend-surface-audit.skeleton.md +32 -0
- package/templates/kinds/gate-result.schema.json +64 -0
- package/templates/kinds/git-publication.contract.json +82 -0
- package/templates/kinds/git-publication.skeleton.md +61 -0
- package/templates/kinds/independent-critique.contract.json +9 -0
- package/templates/kinds/independent-critique.skeleton.md +28 -0
- package/templates/kinds/inventory.schema.json +35 -0
- package/templates/kinds/model.schema.json +99 -0
- package/templates/kinds/mutations.schema.json +65 -0
- package/templates/kinds/platform-operation-receipt.contract.json +76 -0
- package/templates/kinds/platform-operation-receipt.skeleton.md +54 -0
- package/templates/kinds/probes.schema.json +130 -0
- package/templates/kinds/proof.schema.json +17 -0
- package/templates/kinds/quality-verification.contract.json +87 -0
- package/templates/kinds/quality-verification.skeleton.md +58 -0
- package/templates/kinds/release-deployment.contract.json +85 -0
- package/templates/kinds/release-deployment.skeleton.md +67 -0
- package/templates/kinds/route.schema.json +293 -0
- package/templates/kinds/stack-model.schema.json +90 -0
- package/templates/kinds/uat-capture.schema.json +35 -0
- package/templates/kinds/uat-flow-verification.contract.json +11 -0
- package/templates/kinds/uat-flow-verification.skeleton.md +47 -0
- package/templates/kinds/uat-snapshot.schema.json +96 -0
- package/templates/kinds/uat-verdicts.schema.json +41 -0
- package/templates/kinds/ui-coverage.schema.json +77 -0
- package/templates/kinds/verdicts.schema.json +39 -0
- package/templates/kinds/workspace-route-binding.contract.json +12 -0
- package/templates/kinds/workspace-route-binding.skeleton.md +60 -0
- package/templates/kinds/writes.schema.json +37 -0
- package/templates/operator.template.md +73 -0
- package/templates/patterns.template.md +31 -0
- package/templates/step/request.schema.json +42 -0
- package/templates/step/response.schema.json +144 -0
- package/templates/ui-composition.template.md +36 -0
- package/templates/ui-presentation.template.md +57 -0
- package/templates/ui-proof.template.md +34 -0
- package/workflows/README.md +37 -0
- package/workflows/README.vi.md +37 -0
- package/workflows/backend-feature.json +59 -0
- package/workflows/content-unit.json +19 -0
- package/workflows/frontend-new-surface.json +81 -0
- package/workflows/frontend-reconstruct.json +67 -0
- package/workflows/frontend-refine.json +67 -0
- package/workflows/frontend-with-uat.json +78 -0
- package/workflows/full-feature.json +104 -0
- package/workflows/release.json +29 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# platform.operate
|
|
2
|
+
|
|
3
|
+
## Job
|
|
4
|
+
|
|
5
|
+
Operate one bounded shared observability, Sonar, or tunnel service from exact evidence: inventory it,
|
|
6
|
+
converge only the approved delta, prove every check the bound knowledge requires, and stop at the
|
|
7
|
+
smallest owning gap instead of taking product deployment ownership.
|
|
8
|
+
|
|
9
|
+
## Shared infrastructure, not product
|
|
10
|
+
|
|
11
|
+
This operator serves shared infrastructure and never takes product deployment ownership. That
|
|
12
|
+
boundary is not advice: a resource can only be changed if the bound inventory lists it under the same
|
|
13
|
+
service kind, and a product deployment target is never an observability, Sonar, or tunnel resource. A
|
|
14
|
+
plan that reaches for one is invalid input rather than a judgement call at execution time, and a
|
|
15
|
+
request to restart a product service to make room for a shared one leaves through a
|
|
16
|
+
`PRODUCT_DEPLOYMENT_DECLINED` finding rather than through a mutation.
|
|
17
|
+
|
|
18
|
+
## One job, three branches
|
|
19
|
+
|
|
20
|
+
The service kind the inventory records selects the branch, and the three are branches of one job
|
|
21
|
+
rather than three operators.
|
|
22
|
+
Each branch publishes three closed sets, and each is enforced. Observability applies `update-config`,
|
|
23
|
+
`restart-service`, `upsert-dashboard` and `update-remote-write`, proves `service-health`,
|
|
24
|
+
`target-boundary`, `label-boundary`, `remote-write-delivery`, `sample-ordering`, `retry-backoff` and
|
|
25
|
+
`sensitive-data-filter`, and needs `metrics:remote-write`. Sonar applies `create-project`,
|
|
26
|
+
`assign-profile`, `assign-gate` and `enforce-setting`, proves `service-available`, `project-exists`,
|
|
27
|
+
`source-revision`, `profile-assigned`, `gate-assigned` and `enforcement-active`, and needs
|
|
28
|
+
`sonar:project-admin`. Tunnel applies `create-tunnel`, `update-tunnel-route` and `upsert-proxied-dns`,
|
|
29
|
+
proves `dns-target`, `tunnel-route`, `tls` and `public-https`, and needs `tunnel:write` and
|
|
30
|
+
`dns:write`. An effect or a check filed under the wrong branch is invalid input rather than a
|
|
31
|
+
warning, because a cross-filed effect is how an unapproved change acquires the appearance of
|
|
32
|
+
authority. The required proof set is the whole set the branch publishes: the caller cannot ask for
|
|
33
|
+
less, because a green dashboard alone never proved delivery, ordering, or redaction.
|
|
34
|
+
|
|
35
|
+
## Inventory before change
|
|
36
|
+
|
|
37
|
+
A shared service is inventoried before it is changed. The inventory is bound by fingerprint, so the
|
|
38
|
+
receipt states exactly what the service was when the decision was made, and a concurrent revision
|
|
39
|
+
becomes visible as `INVENTORY_DRIFT` rather than being silently overwritten. The recheck happens
|
|
40
|
+
before any mutation, so a differing revision stops the invocation while nothing has changed yet.
|
|
41
|
+
Anything mutated appears in the inventory echo, so a change to a resource nobody looked at first
|
|
42
|
+
cannot be reported as an operation at all. An already-converged service is a proved no-op with no
|
|
43
|
+
mutation, not a failure and not a rewrite, and a converged operation that reports no mutation is
|
|
44
|
+
refused because one of its two statements is false. Application touches only effects inside the
|
|
45
|
+
approved set, one resource at a time, recording the before and after revision of each; a partial
|
|
46
|
+
application is reported as `PARTIAL_MUTATION` with exact revisions and is never hidden behind a
|
|
47
|
+
generic blocker.
|
|
48
|
+
|
|
49
|
+
## A port in use is a coordination finding
|
|
50
|
+
|
|
51
|
+
A port already bound by another process is a fact about a shared machine, not permission to reclaim
|
|
52
|
+
it. The operation records `PORT_COORDINATION_REQUIRED` naming both the port and the process that
|
|
53
|
+
holds it, returns `PORT_CONFLICT`, and stops. It does not stop, kill, restart, or reconfigure the
|
|
54
|
+
holder, and no mutation may target a process observed holding a claimed port. Coordination is the
|
|
55
|
+
required next step and it belongs to the two owners, not to this invocation; `PORT_CONFLICT` is the
|
|
56
|
+
expected outcome on a busy shared machine, not a defect in the plan.
|
|
57
|
+
|
|
58
|
+
## Credentials are resolved, never recorded
|
|
59
|
+
|
|
60
|
+
A capability is a handle and its custody evidence. The credential behind it is resolved for use at
|
|
61
|
+
the moment of the call and is never logged, echoed into evidence, or persisted. The receipt refuses
|
|
62
|
+
the handle as well as the value, because a receipt is durable and a durable record of a capability is
|
|
63
|
+
a leaked credential with a delay; a string carrying credential material anywhere in the request or
|
|
64
|
+
the response is refused as malformed.
|
|
65
|
+
|
|
66
|
+
## The desired state is one approved declaration
|
|
67
|
+
|
|
68
|
+
`desiredState` is the whole of what the person asks for: the approved plan hash, the service kind the
|
|
69
|
+
plan was written against, the resources to converge, the effects to apply, and the two scope sets that
|
|
70
|
+
say which resources may change and which may only be observed. Keeping it as one declaration is what
|
|
71
|
+
makes the approval mean something: `approval` covers that declaration, hash and all, so a field
|
|
72
|
+
edited afterwards no longer matches the hash the approval named. `approval` has no default because
|
|
73
|
+
this is a runtime other sessions and other people share, and changing what a shared service does is
|
|
74
|
+
never something an agent decides alone. `portClaims` defaults to the empty list, because most
|
|
75
|
+
operations need no port at all and a claim nobody made cannot collide with anybody.
|
|
76
|
+
|
|
77
|
+
## Boundary
|
|
78
|
+
|
|
79
|
+
Context is read-only apart from the approved delta. The operator applies only the approved effect
|
|
80
|
+
delta on the inventoried shared service, under an exclusive lease on
|
|
81
|
+
`@worktrees/sessions/central-runtime`, and writes only `response/` of its own branch:
|
|
82
|
+
`data/delta.json`, `data/checks.json`, `response.md` and `response.json`. It does not deploy,
|
|
83
|
+
restart, migrate, or otherwise take ownership of a product service; does not mutate a resource the
|
|
84
|
+
bound inventory does not list; does not emit an effect or a check the bound service kind does not
|
|
85
|
+
publish; does not free a port by stopping, killing, or reconfiguring the process that already holds
|
|
86
|
+
it; does not record a credential value, capability handle, or secret-shaped token anywhere in the
|
|
87
|
+
output; does not edit knowledge or grant its own approval; and does not claim an operated outcome
|
|
88
|
+
while any required check is absent or failed, nor any product readiness, release approval, or UAT
|
|
89
|
+
proof.
|
|
90
|
+
|
|
91
|
+
## Context
|
|
92
|
+
|
|
93
|
+
| Alias | Bind | Required |
|
|
94
|
+
| --- | --- | --- |
|
|
95
|
+
| `@worktrees/sessions/central-runtime` | the shared runtime owner: inventory, generation and health, bound by fingerprint and generation, written only under an exclusive lease | yes |
|
|
96
|
+
| `@workspaces/ports/<project>` | the port projection the runtime binds to | yes |
|
|
97
|
+
| `@workspaces/device-state` | capability handles by name and their custody; values never appear | yes |
|
|
98
|
+
| `@workspaces/projects/<project>/<role>` | which projects the shared services serve | no |
|
|
99
|
+
|
|
100
|
+
## Inputs
|
|
101
|
+
|
|
102
|
+
| Kind | From | Required |
|
|
103
|
+
| --- | --- | --- |
|
|
104
|
+
|
|
105
|
+
## Requirements
|
|
106
|
+
|
|
107
|
+
| Field | Type | Default | Ask |
|
|
108
|
+
| --- | --- | --- | --- |
|
|
109
|
+
| `service` | id | — | The one shared service being operated |
|
|
110
|
+
| `desiredState` | `{planSha256, serviceKind, resourceRefs, effects, mutableResourceRefs, observationOnlyResourceRefs}` | — | The approved declaration: which plan, which branch, which resources, which effects, and what may change against what may only be observed |
|
|
111
|
+
| `portClaims` | list of `{port, resourceRef}` | [] | Which ports the desired state needs, and for which owned resource |
|
|
112
|
+
| `approval` | id | — | The approval that covers this desired state; changing a shared runtime always needs a person |
|
|
113
|
+
| `resume` | token | null | The blocked branch's token when re-entering after a stop |
|
|
114
|
+
|
|
115
|
+
## Steps
|
|
116
|
+
|
|
117
|
+
| # | Step | Params | Reads | Writes | Stops with |
|
|
118
|
+
| --- | --- | --- | --- | --- | --- |
|
|
119
|
+
| 1 | Validate the gate and resume | `resume` | `request/request.json`, @worktrees/sessions/central-runtime at the frozen generation | — | `INVALID_INPUT`, `SOURCE_DRIFT`, `NO_PROGRESS` |
|
|
120
|
+
| 2 | Bind the authority: the runtime, the device state, the projects and the approval | `service`, `approval` | @worktrees/sessions/central-runtime for the inventory fingerprint and generation, @workspaces/device-state for each capability handle with its custody evidence, @workspaces/projects/<project>/<role>, @tools/secrets | — | `AUTHORITY_DRIFT`, `CAPABILITY_MISSING` |
|
|
121
|
+
| 3 | Recheck the inventory once before anything changes | — | @worktrees/sessions/central-runtime, the declared resources re-observed once, @tools/git | — | `INVENTORY_DRIFT` |
|
|
122
|
+
| 4 | Resolve the port claims | `portClaims` | @workspaces/ports/<project> for the claimed ports, @worktrees/sessions/central-runtime for their observed holders | — | `PORT_CONFLICT` |
|
|
123
|
+
| 5 | Derive the delta between what is observed and what is desired | `desiredState` | @worktrees/sessions/central-runtime for the observed state, `request/request.json` for the desired state | `response/data/delta.json` | — |
|
|
124
|
+
| 6 | Apply the approved delta, one resource at a time, under an exclusive lease | — | @worktrees/sessions/central-runtime, @workspaces/device-state for the handles by name | @worktrees/sessions/central-runtime, `response/data/delta.json`, @tools/container, @tools/shell | `EFFECT_UNAUTHORIZED`, `SERVICE_UNAVAILABLE` |
|
|
125
|
+
| 7 | Prove every required check | — | @worktrees/sessions/central-runtime re-read against the branch's complete proof set, @tools/http | `response/data/checks.json` | `PROOF_FAILED` |
|
|
126
|
+
| 8 | Write the receipt and emit | — | everything above | `response/response.md`, `response/response.json` | — |
|
|
127
|
+
|
|
128
|
+
A resume begins again at validation, reuses only unchanged fingerprinted observations, and consumes
|
|
129
|
+
the exact delta; a resume that adds no authority, inventory, desired-state or scope change is
|
|
130
|
+
`NO_PROGRESS`, and a re-observed inventory must arrive as a new fingerprint because the same
|
|
131
|
+
fingerprint cannot yield a different answer.
|
|
132
|
+
|
|
133
|
+
## Outputs
|
|
134
|
+
|
|
135
|
+
| Kind | File | Type | Required |
|
|
136
|
+
| --- | --- | --- | --- |
|
|
137
|
+
| `platform-operation-receipt` | `response/response.md` | md | yes |
|
|
138
|
+
| `delta` | `response/data/delta.json` | data | yes |
|
|
139
|
+
| `checks` | `response/data/checks.json` | data | yes |
|
|
140
|
+
|
|
141
|
+
## Stops
|
|
142
|
+
|
|
143
|
+
| Code | Disposition |
|
|
144
|
+
| --- | --- |
|
|
145
|
+
| `INVALID_INPUT` | terminate |
|
|
146
|
+
| `SOURCE_DRIFT` | terminate |
|
|
147
|
+
| `NO_PROGRESS` | terminate |
|
|
148
|
+
| `AUTHORITY_DRIFT` | terminate |
|
|
149
|
+
| `CAPABILITY_MISSING` | terminate |
|
|
150
|
+
| `INVENTORY_DRIFT` | terminate |
|
|
151
|
+
| `PORT_CONFLICT` | terminate |
|
|
152
|
+
| `EFFECT_UNAUTHORIZED` | terminate |
|
|
153
|
+
| `SERVICE_UNAVAILABLE` | terminate |
|
|
154
|
+
| `PROOF_FAILED` | terminate |
|
|
155
|
+
|
|
156
|
+
## Next
|
|
157
|
+
|
|
158
|
+
| When | Operator |
|
|
159
|
+
| --- | --- |
|
|
160
|
+
| the routed checkout or its head no longer matches the frozen binding | `workspace.bind` |
|
|
161
|
+
| the runtime a frontend surface must be audited against is now serving | `frontend.surface.audit` |
|
|
162
|
+
| the shared service is operated and the release that waited on it may continue | `release.deploy` |
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# platform.operate
|
|
2
|
+
|
|
3
|
+
## Việc
|
|
4
|
+
|
|
5
|
+
Vận hành một dịch vụ dùng chung có ranh giới, thuộc observability, Sonar hay tunnel, từ bằng chứng
|
|
6
|
+
chính xác: kiểm kê nó, hội tụ đúng phần delta đã duyệt, chứng minh mọi phép kiểm mà tri thức đã ràng
|
|
7
|
+
đòi hỏi, và dừng ở khoảng trống sở hữu nhỏ nhất thay vì nhận lấy quyền deploy sản phẩm.
|
|
8
|
+
|
|
9
|
+
## Hạ tầng dùng chung, không phải sản phẩm
|
|
10
|
+
|
|
11
|
+
Operator này phục vụ hạ tầng dùng chung và không bao giờ nhận quyền deploy sản phẩm. Ranh giới đó
|
|
12
|
+
không phải lời khuyên: một tài nguyên chỉ đổi được nếu inventory đã ràng liệt kê nó dưới cùng một
|
|
13
|
+
service kind, và một đích deploy sản phẩm thì không bao giờ là tài nguyên observability, Sonar hay
|
|
14
|
+
tunnel. Một kế hoạch với tay sang đó là đầu vào không hợp lệ chứ không phải chuyện cân nhắc lúc chạy,
|
|
15
|
+
và yêu cầu restart một dịch vụ sản phẩm để dọn chỗ cho một dịch vụ dùng chung sẽ đi ra bằng phát hiện
|
|
16
|
+
`PRODUCT_DEPLOYMENT_DECLINED` chứ không bằng một lần mutate.
|
|
17
|
+
|
|
18
|
+
## Một việc, ba nhánh
|
|
19
|
+
|
|
20
|
+
Kiểu dịch vụ mà inventory ghi lại chọn nhánh, và ba nhánh là ba nhánh của cùng một việc chứ không phải ba operator. Mỗi
|
|
21
|
+
nhánh công bố ba tập đóng, và cả ba đều được thi hành. Observability áp `update-config`,
|
|
22
|
+
`restart-service`, `upsert-dashboard` và `update-remote-write`, chứng minh `service-health`,
|
|
23
|
+
`target-boundary`, `label-boundary`, `remote-write-delivery`, `sample-ordering`, `retry-backoff` và
|
|
24
|
+
`sensitive-data-filter`, và cần `metrics:remote-write`. Sonar áp `create-project`, `assign-profile`,
|
|
25
|
+
`assign-gate` và `enforce-setting`, chứng minh `service-available`, `project-exists`,
|
|
26
|
+
`source-revision`, `profile-assigned`, `gate-assigned` và `enforcement-active`, và cần
|
|
27
|
+
`sonar:project-admin`. Tunnel áp `create-tunnel`, `update-tunnel-route` và `upsert-proxied-dns`,
|
|
28
|
+
chứng minh `dns-target`, `tunnel-route`, `tls` và `public-https`, và cần `tunnel:write` cùng
|
|
29
|
+
`dns:write`. Một effect hay một check nộp nhầm nhánh là đầu vào không hợp lệ chứ không phải cảnh báo,
|
|
30
|
+
vì effect nộp chéo chính là cách một thay đổi chưa duyệt khoác lên mình vẻ ngoài của thẩm quyền. Tập
|
|
31
|
+
chứng minh bắt buộc là trọn tập mà nhánh công bố: người gọi không được xin ít hơn, vì một dashboard
|
|
32
|
+
xanh tự nó chưa bao giờ chứng minh được delivery, ordering hay redaction.
|
|
33
|
+
|
|
34
|
+
## Kiểm kê trước khi đổi
|
|
35
|
+
|
|
36
|
+
Một dịch vụ dùng chung được kiểm kê trước khi bị đổi. Inventory được ràng bằng fingerprint, nên biên
|
|
37
|
+
bản nói đúng dịch vụ là gì tại lúc quyết định, và một revision chạy song song hiện ra thành
|
|
38
|
+
`INVENTORY_DRIFT` thay vì bị ghi đè lặng lẽ. Lần kiểm lại xảy ra trước mọi mutation, nên một revision
|
|
39
|
+
khác đi sẽ dừng lượt chạy khi chưa có gì thay đổi. Mọi thứ bị mutate đều xuất hiện trong phần vọng
|
|
40
|
+
lại của inventory, nên một thay đổi lên tài nguyên chẳng ai nhìn trước thì không thể báo là một lần
|
|
41
|
+
vận hành. Một dịch vụ đã hội tụ sẵn là một no-op đã được chứng minh, không mutation nào, không phải
|
|
42
|
+
lỗi và cũng không phải viết lại; còn một lần vận hành báo là đã hội tụ mà không có mutation nào thì
|
|
43
|
+
bị từ chối vì một trong hai phát biểu của nó là sai. Việc áp chỉ chạm các effect nằm trong tập đã
|
|
44
|
+
duyệt, mỗi lần một tài nguyên, ghi lại revision trước và sau của từng cái; áp một phần được báo là
|
|
45
|
+
`PARTIAL_MUTATION` kèm đúng hai revision và không bao giờ bị giấu sau một mã chặn chung chung.
|
|
46
|
+
|
|
47
|
+
## Một cổng đang bận là phát hiện cần phối hợp
|
|
48
|
+
|
|
49
|
+
Một cổng đã bị tiến trình khác chiếm là một sự thật về máy dùng chung, không phải giấy phép giành lại
|
|
50
|
+
nó. Lần vận hành ghi `PORT_COORDINATION_REQUIRED` nêu cả cổng lẫn tiến trình đang giữ nó, trả
|
|
51
|
+
`PORT_CONFLICT`, rồi dừng. Nó không dừng, không giết, không restart và không cấu hình lại kẻ đang
|
|
52
|
+
giữ, và không mutation nào được nhắm vào một tiến trình đã quan sát thấy đang giữ một cổng được
|
|
53
|
+
claim. Phối hợp là bước kế bắt buộc và nó thuộc về hai người chủ, không thuộc lượt chạy này;
|
|
54
|
+
`PORT_CONFLICT` là kết quả bình thường trên một máy dùng chung bận, không phải khiếm khuyết của kế
|
|
55
|
+
hoạch.
|
|
56
|
+
|
|
57
|
+
## Credential được phân giải, không bao giờ được ghi lại
|
|
58
|
+
|
|
59
|
+
Một capability là một handle cùng bằng chứng custody của nó. Credential đứng sau nó được phân giải để
|
|
60
|
+
dùng đúng lúc gọi và không bao giờ được log, vọng vào evidence hay lưu lại. Biên bản từ chối cả cái
|
|
61
|
+
handle chứ không riêng giá trị, vì biên bản là thứ bền, và một bản ghi bền của một capability là một
|
|
62
|
+
credential rò rỉ có độ trễ; một chuỗi mang vật liệu credential ở bất cứ đâu trong request hay
|
|
63
|
+
response đều bị từ chối như dữ liệu sai dạng.
|
|
64
|
+
|
|
65
|
+
## Trạng thái mong muốn là một khai báo đã duyệt
|
|
66
|
+
|
|
67
|
+
`desiredState` là toàn bộ những gì người ta xin: hash của kế hoạch đã duyệt, kiểu dịch vụ mà kế hoạch
|
|
68
|
+
được viết cho, các resource cần đưa về đúng trạng thái, các effect cần áp, và hai tập phạm vi nói
|
|
69
|
+
resource nào được đổi và resource nào chỉ được quan sát. Giữ nó thành một khai báo duy nhất chính là
|
|
70
|
+
thứ làm cho phê duyệt có nghĩa: `approval` phủ lên đúng khai báo đó, kèm cả hash, nên một field sửa
|
|
71
|
+
sau đó không còn khớp cái hash mà phê duyệt đã gọi tên. `approval` không có mặc định vì đây là một
|
|
72
|
+
runtime mà phiên khác và người khác dùng chung, và đổi việc một dịch vụ dùng chung đang làm không bao
|
|
73
|
+
giờ là chuyện một agent tự quyết. `portClaims` mặc định là danh sách rỗng, vì phần lớn thao tác không
|
|
74
|
+
cần cổng nào, và một claim không ai đặt thì không thể đụng ai.
|
|
75
|
+
|
|
76
|
+
## Ranh giới ghi
|
|
77
|
+
|
|
78
|
+
Context chỉ đọc, trừ phần delta đã duyệt. Operator chỉ áp delta effect đã duyệt lên dịch vụ dùng chung
|
|
79
|
+
đã kiểm kê, dưới một lease độc quyền trên `@worktrees/sessions/central-runtime`, và chỉ ghi
|
|
80
|
+
`response/` của nhánh mình: `data/delta.json`, `data/checks.json`, `response.md` và `response.json`.
|
|
81
|
+
Nó không deploy, restart, migrate hay theo cách nào khác nhận quyền sở hữu một dịch vụ sản phẩm; không
|
|
82
|
+
làm đổi một resource mà inventory đã ràng không liệt kê; không phát một effect hay một check mà nhánh
|
|
83
|
+
dịch vụ đã ràng không công bố; không giải phóng một cổng bằng cách dừng, giết hay cấu hình lại tiến
|
|
84
|
+
trình đang giữ nó; không ghi giá trị credential, handle capability hay token dạng bí mật ở bất kỳ đâu
|
|
85
|
+
trong đầu ra; không sửa knowledge hay tự cấp phê duyệt cho mình; và không tuyên bố một kết quả đã vận
|
|
86
|
+
hành khi còn một check bắt buộc vắng mặt hay hỏng, cũng không tuyên bố readiness sản phẩm, phê duyệt
|
|
87
|
+
release hay bằng chứng UAT nào.
|
|
88
|
+
|
|
89
|
+
## Context
|
|
90
|
+
|
|
91
|
+
| Alias | Bind | Bắt buộc |
|
|
92
|
+
| --- | --- | --- |
|
|
93
|
+
| `@worktrees/sessions/central-runtime` | chủ sở hữu runtime dùng chung: inventory, generation và health, ràng theo fingerprint và generation, chỉ ghi dưới một lease độc quyền | có |
|
|
94
|
+
| `@workspaces/ports/<project>` | phép chiếu cổng mà runtime ràng vào | có |
|
|
95
|
+
| `@workspaces/device-state` | handle capability theo tên và custody của chúng; giá trị không bao giờ xuất hiện | có |
|
|
96
|
+
| `@workspaces/projects/<project>/<role>` | những project mà các dịch vụ dùng chung phục vụ | không |
|
|
97
|
+
|
|
98
|
+
## Đầu vào
|
|
99
|
+
|
|
100
|
+
| Kind | Từ đâu | Bắt buộc |
|
|
101
|
+
| --- | --- | --- |
|
|
102
|
+
|
|
103
|
+
## Yêu cầu
|
|
104
|
+
|
|
105
|
+
| Field | Kiểu | Mặc định | Hỏi |
|
|
106
|
+
| --- | --- | --- | --- |
|
|
107
|
+
| `service` | id | — | Đúng một dịch vụ dùng chung đang được vận hành |
|
|
108
|
+
| `desiredState` | `{planSha256, serviceKind, resourceRefs, effects, mutableResourceRefs, observationOnlyResourceRefs}` | — | Khai báo đã duyệt: kế hoạch nào, nhánh nào, resource nào, effect nào, và cái gì được đổi so với cái gì chỉ được quan sát |
|
|
109
|
+
| `portClaims` | list of `{port, resourceRef}` | [] | Trạng thái mong muốn cần những cổng nào, và cho resource sở hữu nào |
|
|
110
|
+
| `approval` | id | — | Phê duyệt phủ lên trạng thái mong muốn này; đổi một runtime dùng chung luôn cần một con người |
|
|
111
|
+
| `resume` | token | null | Token của nhánh bị chặn khi vào lại sau một mã dừng |
|
|
112
|
+
|
|
113
|
+
## Các bước
|
|
114
|
+
|
|
115
|
+
| # | Bước | Tham số | Đọc | Ghi | Dừng với |
|
|
116
|
+
| --- | --- | --- | --- | --- | --- |
|
|
117
|
+
| 1 | Kiểm cổng vào và resume | `resume` | `request/request.json`, @worktrees/sessions/central-runtime tại generation đã đóng băng | — | `INVALID_INPUT`, `SOURCE_DRIFT`, `NO_PROGRESS` |
|
|
118
|
+
| 2 | Ràng thẩm quyền: runtime, device-state, projects và phê duyệt | `service`, `approval` | @worktrees/sessions/central-runtime cho fingerprint và generation của inventory, @workspaces/device-state cho từng handle capability kèm bằng chứng custody, @workspaces/projects/<project>/<role>, @tools/secrets | — | `AUTHORITY_DRIFT`, `CAPABILITY_MISSING` |
|
|
119
|
+
| 3 | Kiểm lại inventory một lần trước khi có gì thay đổi | — | @worktrees/sessions/central-runtime, các resource đã khai được quan sát lại một lần, @tools/git | — | `INVENTORY_DRIFT` |
|
|
120
|
+
| 4 | Phân giải các port claim | `portClaims` | @workspaces/ports/<project> cho các cổng được claim, @worktrees/sessions/central-runtime cho chủ giữ quan sát được của chúng | — | `PORT_CONFLICT` |
|
|
121
|
+
| 5 | Suy ra delta giữa cái quan sát được và cái mong muốn | `desiredState` | @worktrees/sessions/central-runtime cho trạng thái quan sát được, `request/request.json` cho trạng thái mong muốn | `response/data/delta.json` | — |
|
|
122
|
+
| 6 | Áp delta đã duyệt, từng resource một, dưới một lease độc quyền | — | @worktrees/sessions/central-runtime, @workspaces/device-state cho các handle theo tên | @worktrees/sessions/central-runtime, `response/data/delta.json`, @tools/container, @tools/shell | `EFFECT_UNAUTHORIZED`, `SERVICE_UNAVAILABLE` |
|
|
123
|
+
| 7 | Chứng minh mọi check bắt buộc | — | @worktrees/sessions/central-runtime đọc lại theo bộ chứng minh đầy đủ của nhánh, @tools/http | `response/data/checks.json` | `PROOF_FAILED` |
|
|
124
|
+
| 8 | Viết biên bản và phát | — | mọi thứ ở trên | `response/response.md`, `response/response.json` | — |
|
|
125
|
+
|
|
126
|
+
Một lần resume bắt đầu lại từ cổng vào, chỉ dùng lại quan sát có fingerprint không đổi, và tiêu thụ
|
|
127
|
+
đúng phần delta; một lần resume không thêm thẩm quyền, inventory, trạng thái mong muốn hay phạm vi nào
|
|
128
|
+
là `NO_PROGRESS`, và một inventory quan sát lại phải tới dưới dạng một fingerprint mới vì cùng một
|
|
129
|
+
fingerprint không thể cho một câu trả lời khác.
|
|
130
|
+
|
|
131
|
+
## Đầu ra
|
|
132
|
+
|
|
133
|
+
| Kind | File | Kiểu | Bắt buộc |
|
|
134
|
+
| --- | --- | --- | --- |
|
|
135
|
+
| `platform-operation-receipt` | `response/response.md` | md | có |
|
|
136
|
+
| `delta` | `response/data/delta.json` | data | có |
|
|
137
|
+
| `checks` | `response/data/checks.json` | data | có |
|
|
138
|
+
|
|
139
|
+
## Dừng
|
|
140
|
+
|
|
141
|
+
| Code | Xử lý |
|
|
142
|
+
| --- | --- |
|
|
143
|
+
| `INVALID_INPUT` | terminate |
|
|
144
|
+
| `SOURCE_DRIFT` | terminate |
|
|
145
|
+
| `NO_PROGRESS` | terminate |
|
|
146
|
+
| `AUTHORITY_DRIFT` | terminate |
|
|
147
|
+
| `CAPABILITY_MISSING` | terminate |
|
|
148
|
+
| `INVENTORY_DRIFT` | terminate |
|
|
149
|
+
| `PORT_CONFLICT` | terminate |
|
|
150
|
+
| `EFFECT_UNAUTHORIZED` | terminate |
|
|
151
|
+
| `SERVICE_UNAVAILABLE` | terminate |
|
|
152
|
+
| `PROOF_FAILED` | terminate |
|
|
153
|
+
|
|
154
|
+
## Kế tiếp
|
|
155
|
+
|
|
156
|
+
| Khi | Operator |
|
|
157
|
+
| --- | --- |
|
|
158
|
+
| checkout đã route hay head của nó không còn khớp ràng buộc đã đóng băng | `workspace.bind` |
|
|
159
|
+
| runtime mà một bề mặt frontend phải được audit trên đó nay đã phục vụ | `frontend.surface.audit` |
|
|
160
|
+
| dịch vụ dùng chung đã vận hành xong và release đang chờ nó có thể chạy tiếp | `release.deploy` |
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// Proves validate.mjs on a synthetic session branch: one converged observability operation, one
|
|
2
|
+
// already-converged no-op, one blocked on a port conflict, and one mutation per law, each of which
|
|
3
|
+
// must fail with a line that names the defect.
|
|
4
|
+
import assert from 'node:assert/strict';
|
|
5
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
6
|
+
import { tmpdir } from 'node:os';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { validatePlatformStep, KIND_CHECKS } from './validate.mjs';
|
|
9
|
+
|
|
10
|
+
const PLAN = `sha256:${'0'.repeat(64)}`;
|
|
11
|
+
const FP = `sha256:${'1'.repeat(64)}`;
|
|
12
|
+
const SERVICE = 'prometheus/shared';
|
|
13
|
+
const OWNER = 'platform-team';
|
|
14
|
+
const APPROVAL = '@worktrees/debts/be.md#metrics-approval';
|
|
15
|
+
const EFFECTS = ['update-config', 'update-remote-write'];
|
|
16
|
+
const CAPABILITIES = [{ capability: 'metrics:remote-write', custodyEvidenceRef: 'custody/metrics.json' }];
|
|
17
|
+
|
|
18
|
+
const resource = (ref = SERVICE, kind = 'observability', revision = 'r-14') => ({ resourceRef: ref, kind, revision, ownerRef: OWNER });
|
|
19
|
+
const mutation = (effect, over = {}) => ({ effect, resourceRef: SERVICE, beforeRevision: 'r-14', afterRevision: 'r-15', ...over });
|
|
20
|
+
const check = (name, status = 'passed') => ({ name, resourceRef: SERVICE, status, evidenceRef: `logs/${name}.txt` });
|
|
21
|
+
const checkList = (names = KIND_CHECKS.observability, status = 'passed') => names.map((n) => check(n, status));
|
|
22
|
+
|
|
23
|
+
function delta({
|
|
24
|
+
convergence = 'converged', mutations = EFFECTS.map((e) => mutation(e)), appliedEffects, allowedEffects = EFFECTS,
|
|
25
|
+
capabilities = CAPABILITIES, resources = [resource()], portHolders = [], portClaims = [], serviceKind = 'observability',
|
|
26
|
+
planSha256 = PLAN, approvalRef = APPROVAL, mutableResourceRefs = [SERVICE], serviceRef = SERVICE,
|
|
27
|
+
} = {}) {
|
|
28
|
+
return {
|
|
29
|
+
serviceRef, serviceKind, ownerRef: OWNER, approvalRef, planSha256,
|
|
30
|
+
inventoryFingerprint: FP, generation: 7, observedAt: '2026-01-10T00:00:00.000Z',
|
|
31
|
+
inventoriedResources: resources, observedPortHolders: portHolders, portClaims,
|
|
32
|
+
mutableResourceRefs, observationOnlyResourceRefs: [],
|
|
33
|
+
allowedEffects, appliedEffects: appliedEffects ?? [...new Set(mutations.map((m) => m.effect))],
|
|
34
|
+
capabilities, convergence, mutations,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function checksJson({ serviceKind = 'observability', required = KIND_CHECKS.observability, list = checkList(), findings, serviceRef = SERVICE } = {}) {
|
|
39
|
+
return {
|
|
40
|
+
serviceRef, serviceKind, requiredCheckNames: required, checks: list,
|
|
41
|
+
findings: findings ?? [{ code: 'SHARED_SERVICE_INVENTORIED', resourceRef: SERVICE, port: null, holderRef: null, statement: 'the service was inventoried before anything changed' }],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function responseMd({ convergence = 'converged', mutations = EFFECTS.map((e) => mutation(e)), list = checkList(), findings = [['SHARED_SERVICE_INVENTORIED', SERVICE, '—', '—', 'the service was inventoried before anything changed']], service = SERVICE, kind = 'observability', plan = PLAN, approval = APPROVAL, resources = [resource()], portHolders = [] } = {}) {
|
|
46
|
+
return `# platform-operation-receipt — ${kind} ${service}
|
|
47
|
+
|
|
48
|
+
The shared service was inventoried, the approved delta was applied, and the branch's complete proof
|
|
49
|
+
set was proved on its own evidence.
|
|
50
|
+
|
|
51
|
+
## Binding
|
|
52
|
+
|
|
53
|
+
| Field | Value |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| Operator | \`platform.operate\` |
|
|
56
|
+
| Step | \`step-1/parallel-1\` |
|
|
57
|
+
| Project | \`starci-academy\` |
|
|
58
|
+
| Service | ${service} |
|
|
59
|
+
| Service kind | ${kind} |
|
|
60
|
+
| Owner | \`${OWNER}\` |
|
|
61
|
+
| Approval | ${approval} |
|
|
62
|
+
| Desired state | \`${plan}\` |
|
|
63
|
+
| Inventory fingerprint | \`${FP}\` |
|
|
64
|
+
|
|
65
|
+
## Convergence
|
|
66
|
+
|
|
67
|
+
| Field | Value |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| Convergence | ${convergence} |
|
|
70
|
+
|
|
71
|
+
## Inventoried resources
|
|
72
|
+
|
|
73
|
+
| Resource | Kind | Revision | Owner |
|
|
74
|
+
| --- | --- | --- | --- |
|
|
75
|
+
${resources.map((r) => `| \`${r.resourceRef}\` | ${r.kind} | ${r.revision} | \`${r.ownerRef}\` |`).join('\n')}
|
|
76
|
+
|
|
77
|
+
## Port holders
|
|
78
|
+
|
|
79
|
+
| Port | Holder | Evidence |
|
|
80
|
+
| --- | --- | --- |
|
|
81
|
+
${portHolders.map((h) => `| ${h.port} | \`${h.holderRef}\` | \`${h.evidenceRef}\` |`).join('\n')}
|
|
82
|
+
|
|
83
|
+
## Mutations
|
|
84
|
+
|
|
85
|
+
| Effect | Resource | Before | After |
|
|
86
|
+
| --- | --- | --- | --- |
|
|
87
|
+
${mutations.map((m) => `| \`${m.effect}\` | \`${m.resourceRef}\` | ${m.beforeRevision ?? '—'} | ${m.afterRevision} |`).join('\n')}
|
|
88
|
+
|
|
89
|
+
## Checks
|
|
90
|
+
|
|
91
|
+
| Check | Resource | Status | Evidence |
|
|
92
|
+
| --- | --- | --- | --- |
|
|
93
|
+
${list.map((c) => `| \`${c.name}\` | \`${c.resourceRef}\` | ${c.status} | \`${c.evidenceRef}\` |`).join('\n')}
|
|
94
|
+
|
|
95
|
+
## Findings
|
|
96
|
+
|
|
97
|
+
| Code | Resource | Port | Holder | Statement |
|
|
98
|
+
| --- | --- | --- | --- | --- |
|
|
99
|
+
${findings.map(([code, res, port, holder, statement]) => `| \`${code}\` | \`${res}\` | ${port} | ${holder} | ${statement} |`).join('\n')}
|
|
100
|
+
`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const requestJson = ({ kind = 'observability', effects = EFFECTS, resourceRefs = [SERVICE], mutableResourceRefs = [SERVICE], observationOnlyResourceRefs = [], portClaims = [], plan = PLAN, service = SERVICE, approval = APPROVAL, extra = {} } = {}) => ({
|
|
104
|
+
schemaVersion: 9, operatorId: 'platform.operate', step: 1, parallel: 1, sessionId: 's-test',
|
|
105
|
+
contexts: [{ alias: '@worktrees/sessions/central-runtime', head: null }],
|
|
106
|
+
requirements: {
|
|
107
|
+
service, approval, portClaims, resume: null,
|
|
108
|
+
desiredState: { planSha256: plan, serviceKind: kind, resourceRefs, effects, mutableResourceRefs, observationOnlyResourceRefs },
|
|
109
|
+
...extra,
|
|
110
|
+
},
|
|
111
|
+
inputs: {}, resume: null,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const responseJson = ({ status = 'done', stop, next = ['release.deploy'] } = {}) => ({
|
|
115
|
+
schemaVersion: 9, operatorId: 'platform.operate', step: 1, parallel: 1, status, ...(stop ? { stop } : {}),
|
|
116
|
+
fallbacks: [],
|
|
117
|
+
fields: status === 'blocked' ? {} : { 'platform-operation-receipt': 'response/response.md', delta: 'response/data/delta.json', checks: 'response/data/checks.json' },
|
|
118
|
+
commits: [], next,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
function writeBranch(files) {
|
|
122
|
+
const session = mkdtempSync(path.join(tmpdir(), 'platform-session-'));
|
|
123
|
+
const branch = path.join(session, 'step-1', 'parallel-1');
|
|
124
|
+
for (const d of ['request', 'response/data', 'response/artifacts']) mkdirSync(path.join(branch, d), { recursive: true });
|
|
125
|
+
writeFileSync(path.join(session, 'state.json'), JSON.stringify({ id: 's-test', chain: [['1/1']], steps: { '1/1': 'platform.operate' }, current: '1/1', status: 'running' }));
|
|
126
|
+
for (const [name, content] of Object.entries(files)) {
|
|
127
|
+
if (content === null) continue;
|
|
128
|
+
writeFileSync(path.join(branch, name), typeof content === 'string' ? content : JSON.stringify(content, null, 2));
|
|
129
|
+
}
|
|
130
|
+
return { branch, session };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const baseline = (over = {}) => ({
|
|
134
|
+
'request/request.json': requestJson(),
|
|
135
|
+
'response/response.json': responseJson(),
|
|
136
|
+
'response/response.md': responseMd(),
|
|
137
|
+
'response/data/delta.json': delta(),
|
|
138
|
+
'response/data/checks.json': checksJson(),
|
|
139
|
+
...over,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const alreadyConverged = () => baseline({
|
|
143
|
+
'request/request.json': requestJson({ effects: [] }),
|
|
144
|
+
'response/data/delta.json': delta({ convergence: 'already-converged', mutations: [] }),
|
|
145
|
+
'response/data/checks.json': checksJson({ findings: [{ code: 'ALREADY_CONVERGED', resourceRef: SERVICE, port: null, holderRef: null, statement: 'the service already matched the approved plan' }] }),
|
|
146
|
+
'response/response.md': responseMd({ convergence: 'already-converged', mutations: [], findings: [['ALREADY_CONVERGED', SERVICE, '—', '—', 'the service already matched the approved plan']] }),
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
async function expectValid(files, label) {
|
|
150
|
+
const { branch, session } = writeBranch(files);
|
|
151
|
+
const { errors } = await validatePlatformStep(branch);
|
|
152
|
+
rmSync(session, { recursive: true, force: true });
|
|
153
|
+
assert.deepEqual(errors, [], `${label} should be valid`);
|
|
154
|
+
}
|
|
155
|
+
async function expectError(files, needle, label) {
|
|
156
|
+
const { branch, session } = writeBranch(files);
|
|
157
|
+
const { errors } = await validatePlatformStep(branch);
|
|
158
|
+
rmSync(session, { recursive: true, force: true });
|
|
159
|
+
assert.ok(errors.some((e) => e.includes(needle)), `${label}: expected an error containing "${needle}", got:\n${errors.join('\n') || '(none)'}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
await expectValid(baseline(), 'a converged observability operation with the complete proof set');
|
|
163
|
+
await expectValid(alreadyConverged(), 'an already-converged service as a proved no-op');
|
|
164
|
+
await expectValid({
|
|
165
|
+
'request/request.json': requestJson(),
|
|
166
|
+
'response/response.json': responseJson({ status: 'blocked', stop: 'PORT_CONFLICT', next: [] }),
|
|
167
|
+
'response/response.md': null, 'response/data/delta.json': null, 'response/data/checks.json': null,
|
|
168
|
+
}, 'blocked on a port conflict before anything changed');
|
|
169
|
+
|
|
170
|
+
await expectError(baseline({ 'response/response.json': { ...responseJson(), stop: 'PROOF_FAILED' } }), 'only a blocked response carries a stop', 'done with a stop');
|
|
171
|
+
await expectError(baseline({ 'response/response.json': responseJson({ status: 'blocked', stop: 'PORT_SEIZED', next: [] }) }), 'not a registered code', 'unknown stop code');
|
|
172
|
+
await expectError(baseline({ 'request/request.json': requestJson({ extra: { credential: 'token: abcdefghijklmnop' } }) }), 'requirements.credential is not a field', 'a credential has nowhere to go in the request');
|
|
173
|
+
await expectError(baseline({ 'request/request.json': requestJson({ approval: null }) }), 'required field approval has no value', 'a shared runtime change nobody approved');
|
|
174
|
+
await expectError(baseline({ 'request/request.json': requestJson({ extra: { serviceKind: 'observability' } }) }), 'requirements.serviceKind is not a field', 'a field the operator no longer declares');
|
|
175
|
+
await expectError(baseline({ 'request/request.json': requestJson({ effects: ['assign-gate'] }) }), 'does not belong to the observability service kind', 'a Sonar effect filed under observability');
|
|
176
|
+
await expectError(baseline({ 'request/request.json': requestJson({ mutableResourceRefs: ['other/service'], resourceRefs: [SERVICE] }) }), 'must be inside desiredState.mutableResourceRefs', 'the service outside its own mutable ceiling');
|
|
177
|
+
await expectError(baseline({ 'request/request.json': requestJson({ observationOnlyResourceRefs: [SERVICE] }) }), 'cannot be both mutable and observation-only', 'a resource in both scope sets');
|
|
178
|
+
await expectError(baseline({ 'request/request.json': requestJson({ portClaims: [{ port: 9090, resourceRef: 'product/web' }] }) }), 'which this operation does not own', 'a port claimed for a foreign resource');
|
|
179
|
+
await expectError(baseline({ 'request/request.json': requestJson({ portClaims: [{ port: 9090, resourceRef: SERVICE }, { port: 9090, resourceRef: SERVICE }] }) }), 'must not claim the same port twice', 'the same port claimed twice');
|
|
180
|
+
await expectError(baseline({ 'response/data/delta.json': delta({ resources: [resource('other/service')] }) }), 'was not inventoried before the operation', 'the operated service never inventoried');
|
|
181
|
+
await expectError(baseline({ 'response/data/delta.json': delta({ resources: [resource(SERVICE, 'sonar')] }) }), 'belongs to the sonar branch and is outside this observability operation', 'a resource from another branch');
|
|
182
|
+
await expectError(baseline({ 'response/data/delta.json': delta({ planSha256: `sha256:${'9'.repeat(64)}` }) }), 'planSha256 must equal the approved plan hash', 'a plan hash nobody approved');
|
|
183
|
+
await expectError(baseline({ 'response/data/delta.json': delta({ approvalRef: '@worktrees/debts/be.md#other' }) }), 'the bound approval is not the one the request declared', 'an approval borrowed from another plan');
|
|
184
|
+
await expectError(baseline({ 'request/request.json': requestJson({ effects: ['update-config', 'restart-service'] }), 'response/data/delta.json': delta({ allowedEffects: EFFECTS }) }), 'is outside the approved effect set', 'an effect outside the approval');
|
|
185
|
+
await expectError(baseline({ 'response/data/delta.json': delta({ capabilities: [] }) }), 'requires the metrics:remote-write capability', 'a branch running without its capability');
|
|
186
|
+
await expectError(baseline({ 'response/data/checks.json': checksJson({ required: KIND_CHECKS.observability.slice(0, 3), list: checkList(KIND_CHECKS.observability.slice(0, 3)) }) }), 'must require the remote-write-delivery check', 'a narrowed proof set');
|
|
187
|
+
await expectError(baseline({ 'response/data/checks.json': checksJson({ list: checkList(KIND_CHECKS.observability.slice(0, 6)) }), 'response/response.md': responseMd({ list: checkList(KIND_CHECKS.observability.slice(0, 6)) }) }), 'cannot be proved without the sensitive-data-filter check', 'an operated outcome missing a check');
|
|
188
|
+
await expectError(baseline({ 'response/data/checks.json': checksJson({ list: checkList(KIND_CHECKS.observability, 'failed') }), 'response/response.md': responseMd({ list: checkList(KIND_CHECKS.observability, 'failed') }) }), 'so the operation cannot be reported as operated', 'an operated outcome over a failed check');
|
|
189
|
+
await expectError(baseline({ 'response/data/delta.json': delta({ mutations: [mutation('update-config'), mutation('update-remote-write', { resourceRef: 'ghost/service' })] }) }), 'was mutated without being inventoried first', 'a mutation on a resource nobody inventoried');
|
|
190
|
+
await expectError(baseline({ 'response/data/delta.json': delta({ portHolders: [{ port: 9090, holderRef: SERVICE, evidenceRef: 'ss.txt' }] }) }), 'holds a claimed port and must never be mutated to free it', 'a port freed by mutating its holder');
|
|
191
|
+
await expectError(baseline({ 'response/data/delta.json': delta({ appliedEffects: [...EFFECTS, 'restart-service'] }) }), 'records no mutation', 'an applied effect with no mutation');
|
|
192
|
+
await expectError(baseline({ 'response/data/delta.json': delta({ convergence: 'already-converged' }) }), 'already-converged operation cannot report a mutation', 'a no-op that mutated');
|
|
193
|
+
await expectError(baseline({ 'response/data/delta.json': delta({ convergence: 'converged', mutations: [] }), 'response/response.md': responseMd({ mutations: [] }) }), 'converged operation must report the mutation that converged it', 'convergence with nothing applied');
|
|
194
|
+
await expectError(baseline({ 'response/data/checks.json': checksJson({ findings: [{ code: 'PORT_COORDINATION_REQUIRED', resourceRef: SERVICE, port: 9090, holderRef: 'product/web', statement: 'the port is held elsewhere' }] }), 'response/response.md': responseMd({ findings: [['PORT_COORDINATION_REQUIRED', SERVICE, '9090', '`product/web`', 'the port is held elsewhere']] }) }), 'cannot end in an operated outcome', 'a port coordination finding on an operated receipt');
|
|
195
|
+
await expectError(baseline({ 'response/data/checks.json': checksJson({ findings: [{ code: 'SHARED_SERVICE_INVENTORIED', resourceRef: 'ghost/service', port: null, holderRef: null, statement: 'x' }] }) }), 'names an uninventoried resource', 'a finding on a resource nobody inventoried');
|
|
196
|
+
await expectError(baseline({ 'response/data/delta.json': delta({ capabilities: [{ capability: 'metrics:remote-write', custodyEvidenceRef: 'capability://metrics/remote-write' }] }) }), 'records a credential, which the receipt refuses', 'a capability handle inside the delta');
|
|
197
|
+
await expectError(baseline({ 'response/response.md': responseMd({ service: 'other/service' }) }), 'Binding names a service the request did not operate', 'a receipt for another service');
|
|
198
|
+
await expectError(baseline({ 'response/response.md': responseMd().replace('## Checks', '## Proofs') }), 'missing section ^## Checks$', 'receipt section renamed');
|
|
199
|
+
await expectError(baseline({ 'response/data/delta.json': { ...delta(), inventoryFingerprint: 'nope' } }), 'inventoryFingerprint', 'delta schema');
|
|
200
|
+
await expectError(baseline({ 'response/response.json': (() => { const o = responseJson(); delete o.fields.checks; return o; })() }), 'required output checks is not in fields', 'missing required output');
|
|
201
|
+
|
|
202
|
+
process.stdout.write('platform.operate self-test: 3 valid branches, 28 rejected mutations\n');
|