Prove Target Revenue adoption and prepare bounded issue-core delivery
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
This commit is contained in:
parent
4d2bb19f8a
commit
1dc1d2f3a1
26 changed files with 1989 additions and 21 deletions
|
|
@ -12,6 +12,7 @@ spec:
|
|||
- https://forgejo.coulomb.social/coulomb/*.git
|
||||
# Emergency rollback only (T12 Option A through 2026-08-07)
|
||||
- https://gitea.coulomb.social/coulomb/*.git
|
||||
- ssh://git@forgejo.coulomb.social:30022/coulomb/rapp-issue-core.git
|
||||
destinations:
|
||||
- server: https://kubernetes.default.svc
|
||||
namespace: "*"
|
||||
|
|
|
|||
|
|
@ -54,3 +54,45 @@ receipts, concurrent release, restart, failed health and successful rollback.
|
|||
Run destructive failure fixtures in an isolated environment; production must not
|
||||
be intentionally broken to manufacture proof. Admit only after tests and the
|
||||
observation gate pass. Access, budget or scope expansion stays a monthly decision.
|
||||
|
||||
## Deterministic platform readiness gate
|
||||
|
||||
`python3 scripts/activity_promotion_readiness.py --live --observation observation.json
|
||||
--receipt readiness.json` verifies only the current bounded adoption and reports
|
||||
an explicit owner-proof wait until ACTIVITY-WP-0041-T03 acceptance exists. This
|
||||
read-only check neither creates a principal nor activates a release worker. D2
|
||||
removes the founder reapproval; the identity/custody and runtime proofs above
|
||||
remain required. ACTIVITY-WP-0042 scheduling acceptance does not supply them.
|
||||
|
||||
Later activation checks require operator-owned `--trusted-owner-key` (raw
|
||||
32-byte Ed25519 public key) and `--trusted-owner-principal`, supplied separately
|
||||
from `--owner-acceptance`. No signer key supplied inside a receipt is trusted.
|
||||
The acceptance envelope has exactly `payload` and `signature` (base64 Ed25519
|
||||
signature of sorted compact JSON). Its payload uses
|
||||
`schema_version: activity-core-release-acceptance/v1`, binds the existing
|
||||
`owner_task: ACTIVITY-WP-0041-T03`, issuer, dedicated release principal,
|
||||
`authority: ACTIVITY-WP-0041-image-only-v1`, application, exact estate cluster UID,
|
||||
adoption revision and full broker implementation revision. It expires within
|
||||
five minutes; the operator owns signer admission/revocation and actual native
|
||||
evidence verification. An agent must not synthesize this production acceptance
|
||||
from test fixtures or a producer's booleans.
|
||||
|
||||
The `targets` mapping is the exact single platform child
|
||||
`targetRevision` path on `main`, selective root sync of only activity-core,
|
||||
child activity-core and prune false, as named in the checker. `checks` must
|
||||
contain exactly its named acceptance checks, all actual owner-verified: bounded
|
||||
identity/custody/revocation/denials, independent authenticated receipts/CI digest
|
||||
binding, continuous observer, image-only scope, source CAS/single-flight,
|
||||
restart/lost-response recovery, retention, real failed-health rollback, Temporal
|
||||
registration, durable audit sink and report/schedule invariants.
|
||||
`continuous_health` binds the adoption revision and a current actual 24h sampled
|
||||
interval with at most 90-second gaps. A source timestamp or snapshot is not this
|
||||
proof. Signatures authenticate the accepted owner's assertions; the owner still
|
||||
must ground them in actual runtime receipts.
|
||||
|
||||
Native adoption mismatch or malformed/forged/stale/widened owner acceptance
|
||||
returns unverified. Missing owner acceptance/trust binding returns waiting on
|
||||
the existing owner task, with its cross-owner coordination case. Only ready
|
||||
returns exit code zero. No automatic-sync source candidate is generated:
|
||||
ACTIVITY-WP-0041's current broker contract still relies on explicit selective
|
||||
manual child/root sync and forbids an automated child policy.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
# Activity-core bounded promotion readiness
|
||||
|
||||
```sh
|
||||
python3 scripts/activity_promotion_readiness.py --live --observation observation.json --receipt readiness.json
|
||||
python3 -m unittest discover -s tests -p 'test_activity_promotion_readiness.py'
|
||||
```
|
||||
|
||||
The current result is waiting for ACTIVITY-WP-0041-T03 authenticated owner
|
||||
acceptance. Live scoped adoption, elapsed 24h and Deployment readiness pass;
|
||||
no automation/identity/principal has been activated. A pair of snapshots or an
|
||||
old successful-sync timestamp cannot prove the owner's gap-sensitive observer.
|
||||
Raw subprocess output/errors and credential/Secret objects are never emitted.
|
||||
The initial strict future-timestamp refusal is retained. The revised collector
|
||||
records both the remote timestamp and the local observer's receipt time, avoiding
|
||||
unjustified clock tolerance or backfilled health.
|
||||
|
||||
For later activation, the operator supplies an independent trusted owner public
|
||||
key/principal and signed native acceptance. The checker never loads a public key
|
||||
from the submitted receipt, creates an identity, grants authority or changes a
|
||||
child/root spec. Exact fields and source-derived proof requirements are recorded
|
||||
in `docs/activity-core-release-admission.md`. Only `status: ready` exits zero;
|
||||
waiting/unverified cannot activate anything. Generated fixture signatures in the
|
||||
nine tests are not native acceptance evidence. The receipt and coordination case
|
||||
accompany executable source changes, not an evidence-only checkpoint.
|
||||
|
||||
Abort owner: railiance-platform. Rollback of preparation: revert this gate source
|
||||
change. Later bounded dispatcher activation requires its declared owner rollback
|
||||
(disable admission and recover the prior pinned source through the reviewed
|
||||
broker); no native activation or credential scope expansion follows from this
|
||||
preparation. Existing Application remains manually reconciled, prune disabled.
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"schema_version": "cross-owner-wait-receipt/v0.1",
|
||||
"scenario_type": "cross_owner_wait",
|
||||
"case_id": "RPF-WP-0048-T02--ACTIVITY-WP-0041-T03",
|
||||
"state": "waiting",
|
||||
"reason": "authenticated-owner-activation-acceptance-missing",
|
||||
"beneficiary_workplan_id": "RPF-WP-0048",
|
||||
"beneficiary_task_id": "RPF-WP-0048-T02",
|
||||
"beneficiary_repo": "railiance-platform",
|
||||
"obligor_workplan_id": "ACTIVITY-WP-0041",
|
||||
"obligor_task_id": "ACTIVITY-WP-0041-T03",
|
||||
"obligor_repo": "activity-core",
|
||||
"owner_observation_refs": [
|
||||
"activity-core/docs/release-broker.md",
|
||||
"activity-core/docs/gitops-release.md",
|
||||
"activity-core/docs/platform-recovery-scheduling-plan.md"
|
||||
],
|
||||
"needs_human": false,
|
||||
"automatic_wakeup": false,
|
||||
"foreign_task_changed": false,
|
||||
"checkpoint": {
|
||||
"summary": "Live scoped adoption eligible; authenticated bounded-release owner proof missing. Deterministic fail-closed readiness gate implemented.",
|
||||
"files_changed": [
|
||||
"scripts/activity_promotion_readiness.py",
|
||||
"tests/test_activity_promotion_readiness.py",
|
||||
"workplans/RPF-WP-0048-activity-core-gitops-adoption.md"
|
||||
],
|
||||
"next_action": "ACTIVITY-WP-0041-T03 owner supplies native identity/custody/issuers/continuous observation/rollback acceptance, then platform reruns gate before activation.",
|
||||
"blocked_reason": "ACTIVITY-WP-0041-T03 authenticated owner activation acceptance absent."
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
{
|
||||
"application_uid": "cd179999-852d-4d70-9017-29e867475bff",
|
||||
"automated_present": false,
|
||||
"captured_at": "2026-10-11T19:44:18.628842+00:00",
|
||||
"cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786",
|
||||
"condition_types": [],
|
||||
"deployments": [
|
||||
{
|
||||
"available": 1,
|
||||
"generation_observed": true,
|
||||
"name": "actcore-api",
|
||||
"ready": 1,
|
||||
"replicas": 1,
|
||||
"updated": 1
|
||||
},
|
||||
{
|
||||
"available": 1,
|
||||
"generation_observed": true,
|
||||
"name": "actcore-worker",
|
||||
"ready": 1,
|
||||
"replicas": 1,
|
||||
"updated": 1
|
||||
},
|
||||
{
|
||||
"available": 1,
|
||||
"generation_observed": true,
|
||||
"name": "actcore-event-router",
|
||||
"ready": 1,
|
||||
"replicas": 1,
|
||||
"updated": 1
|
||||
}
|
||||
],
|
||||
"destination": {
|
||||
"namespace": "activity-core",
|
||||
"server": "https://kubernetes.default.svc"
|
||||
},
|
||||
"estate_ip": "92.205.62.239",
|
||||
"health": "Healthy",
|
||||
"operation": {
|
||||
"finishedAt": "2026-10-04T14:13:25Z",
|
||||
"phase": "Succeeded"
|
||||
},
|
||||
"pending_operation": false,
|
||||
"project": "activity-core",
|
||||
"project_scope_matches": true,
|
||||
"resources": [
|
||||
{
|
||||
"group": null,
|
||||
"kind": "ConfigMap",
|
||||
"name": "actcore-external-activity-definitions",
|
||||
"namespace": "activity-core",
|
||||
"status": "Synced"
|
||||
},
|
||||
{
|
||||
"group": null,
|
||||
"kind": "ConfigMap",
|
||||
"name": "actcore-ops-service-inventory",
|
||||
"namespace": "activity-core",
|
||||
"status": "Synced"
|
||||
},
|
||||
{
|
||||
"group": null,
|
||||
"kind": "ConfigMap",
|
||||
"name": "actcore-report-schemas",
|
||||
"namespace": "activity-core",
|
||||
"status": "Synced"
|
||||
},
|
||||
{
|
||||
"group": null,
|
||||
"kind": "ConfigMap",
|
||||
"name": "actcore-runtime-config",
|
||||
"namespace": "activity-core",
|
||||
"status": "Synced"
|
||||
},
|
||||
{
|
||||
"group": null,
|
||||
"kind": "Service",
|
||||
"name": "actcore-api",
|
||||
"namespace": "activity-core",
|
||||
"status": "Synced"
|
||||
},
|
||||
{
|
||||
"group": null,
|
||||
"kind": "Service",
|
||||
"name": "actcore-worker-metrics",
|
||||
"namespace": "activity-core",
|
||||
"status": "Synced"
|
||||
},
|
||||
{
|
||||
"group": "apps",
|
||||
"kind": "Deployment",
|
||||
"name": "actcore-api",
|
||||
"namespace": "activity-core",
|
||||
"status": "Synced"
|
||||
},
|
||||
{
|
||||
"group": "apps",
|
||||
"kind": "Deployment",
|
||||
"name": "actcore-event-router",
|
||||
"namespace": "activity-core",
|
||||
"status": "Synced"
|
||||
},
|
||||
{
|
||||
"group": "apps",
|
||||
"kind": "Deployment",
|
||||
"name": "actcore-worker",
|
||||
"namespace": "activity-core",
|
||||
"status": "Synced"
|
||||
}
|
||||
],
|
||||
"revision": "a86a14332d76d92a7ed176ea34dca4429902c396",
|
||||
"source": {
|
||||
"path": "k8s/gitops",
|
||||
"repoURL": "https://forgejo.coulomb.social/coulomb/activity-core.git",
|
||||
"targetRevision": "a86a14332d76d92a7ed176ea34dca4429902c396"
|
||||
},
|
||||
"sync": "Synced",
|
||||
"sync_options": [
|
||||
"CreateNamespace=false",
|
||||
"ApplyOutOfSyncOnly=true",
|
||||
"PruneLast=true",
|
||||
"FailOnSharedResource=true"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
{
|
||||
"application_uid": "cd179999-852d-4d70-9017-29e867475bff",
|
||||
"automated_present": false,
|
||||
"captured_at": "2026-10-11T19:44:50.828034+00:00",
|
||||
"cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786",
|
||||
"condition_types": [],
|
||||
"deployments": [
|
||||
{
|
||||
"available": 1,
|
||||
"generation_observed": true,
|
||||
"name": "actcore-api",
|
||||
"ready": 1,
|
||||
"replicas": 1,
|
||||
"updated": 1
|
||||
},
|
||||
{
|
||||
"available": 1,
|
||||
"generation_observed": true,
|
||||
"name": "actcore-worker",
|
||||
"ready": 1,
|
||||
"replicas": 1,
|
||||
"updated": 1
|
||||
},
|
||||
{
|
||||
"available": 1,
|
||||
"generation_observed": true,
|
||||
"name": "actcore-event-router",
|
||||
"ready": 1,
|
||||
"replicas": 1,
|
||||
"updated": 1
|
||||
}
|
||||
],
|
||||
"destination": {
|
||||
"namespace": "activity-core",
|
||||
"server": "https://kubernetes.default.svc"
|
||||
},
|
||||
"estate_ip": "92.205.62.239",
|
||||
"health": "Healthy",
|
||||
"operation": {
|
||||
"finishedAt": "2026-10-04T14:13:25Z",
|
||||
"phase": "Succeeded"
|
||||
},
|
||||
"pending_operation": false,
|
||||
"project": "activity-core",
|
||||
"project_scope_matches": true,
|
||||
"resources": [
|
||||
{
|
||||
"group": null,
|
||||
"kind": "ConfigMap",
|
||||
"name": "actcore-external-activity-definitions",
|
||||
"namespace": "activity-core",
|
||||
"status": "Synced"
|
||||
},
|
||||
{
|
||||
"group": null,
|
||||
"kind": "ConfigMap",
|
||||
"name": "actcore-ops-service-inventory",
|
||||
"namespace": "activity-core",
|
||||
"status": "Synced"
|
||||
},
|
||||
{
|
||||
"group": null,
|
||||
"kind": "ConfigMap",
|
||||
"name": "actcore-report-schemas",
|
||||
"namespace": "activity-core",
|
||||
"status": "Synced"
|
||||
},
|
||||
{
|
||||
"group": null,
|
||||
"kind": "ConfigMap",
|
||||
"name": "actcore-runtime-config",
|
||||
"namespace": "activity-core",
|
||||
"status": "Synced"
|
||||
},
|
||||
{
|
||||
"group": null,
|
||||
"kind": "Service",
|
||||
"name": "actcore-api",
|
||||
"namespace": "activity-core",
|
||||
"status": "Synced"
|
||||
},
|
||||
{
|
||||
"group": null,
|
||||
"kind": "Service",
|
||||
"name": "actcore-worker-metrics",
|
||||
"namespace": "activity-core",
|
||||
"status": "Synced"
|
||||
},
|
||||
{
|
||||
"group": "apps",
|
||||
"kind": "Deployment",
|
||||
"name": "actcore-api",
|
||||
"namespace": "activity-core",
|
||||
"status": "Synced"
|
||||
},
|
||||
{
|
||||
"group": "apps",
|
||||
"kind": "Deployment",
|
||||
"name": "actcore-event-router",
|
||||
"namespace": "activity-core",
|
||||
"status": "Synced"
|
||||
},
|
||||
{
|
||||
"group": "apps",
|
||||
"kind": "Deployment",
|
||||
"name": "actcore-worker",
|
||||
"namespace": "activity-core",
|
||||
"status": "Synced"
|
||||
}
|
||||
],
|
||||
"revision": "a86a14332d76d92a7ed176ea34dca4429902c396",
|
||||
"source": {
|
||||
"path": "k8s/gitops",
|
||||
"repoURL": "https://forgejo.coulomb.social/coulomb/activity-core.git",
|
||||
"targetRevision": "a86a14332d76d92a7ed176ea34dca4429902c396"
|
||||
},
|
||||
"sync": "Synced",
|
||||
"sync_options": [
|
||||
"CreateNamespace=false",
|
||||
"ApplyOutOfSyncOnly=true",
|
||||
"PruneLast=true",
|
||||
"FailOnSharedResource=true"
|
||||
],
|
||||
"remote_captured_at": "2026-10-11T19:44:52.074977+00:00"
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"schema_version": "activity-promotion-readiness/v1",
|
||||
"task": "RPF-WP-0048-T02",
|
||||
"owner_dependency": "ACTIVITY-WP-0041-T03",
|
||||
"native_mutation": false,
|
||||
"automatic_sync_candidate": false,
|
||||
"observed_at": "2026-10-11T19:44:17.229169+00:00",
|
||||
"checker_sha256": "ed159e8128c0fbb2741fcff17e5b2eebcee279057da3c1c6bce285e48b233882",
|
||||
"status": "unverified",
|
||||
"reason": "adoption_metadata_unverified"
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"schema_version": "activity-promotion-readiness/v1",
|
||||
"task": "RPF-WP-0048-T02",
|
||||
"owner_dependency": "ACTIVITY-WP-0041-T03",
|
||||
"native_mutation": false,
|
||||
"automatic_sync_candidate": false,
|
||||
"observed_at": "2026-10-11T19:44:50.828539+00:00",
|
||||
"checker_sha256": "93986567cc6ea01b646cebd2e47eb4a5f19ad43b54ecfe420079a45b60ce30f3",
|
||||
"adoption_24h_eligible": true,
|
||||
"adoption_snapshot_is_not_continuous_observation": true,
|
||||
"observation_sha256": "a66b0d128292e24f7964ca90602166ff8b0d88799f31f605dd9fdf88f0b53c25",
|
||||
"status": "waiting",
|
||||
"reason": "authenticated_owner_activation_acceptance_missing",
|
||||
"depends_on": [
|
||||
"ACTIVITY-WP-0041-T03"
|
||||
],
|
||||
"needs_human": false
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"schema_version": "activity-promotion-validation/v1",
|
||||
"task": "RPF-WP-0048-T02",
|
||||
"task_closed": false,
|
||||
"task_status": "wait",
|
||||
"depends_on": [
|
||||
"ACTIVITY-WP-0041-T03"
|
||||
],
|
||||
"needs_human": false,
|
||||
"native_mutation": false,
|
||||
"child_source_changed": false,
|
||||
"tests": {
|
||||
"passed": 9,
|
||||
"failed": 0
|
||||
},
|
||||
"compilation": "passed",
|
||||
"live_checks": "adoption_24h_eligible",
|
||||
"activation": "waiting-for-authenticated-owner-proof",
|
||||
"readiness_ref": "readiness.json",
|
||||
"coordination_case_ref": "coordination-case.json",
|
||||
"checker_sha256": "93986567cc6ea01b646cebd2e47eb4a5f19ad43b54ecfe420079a45b60ce30f3",
|
||||
"source_contracts": {
|
||||
"activity-core/docs/release-broker.md": "bbc917f9b2a533b65d41deb79324b6bd43f8ad14e83587387aa9078fff238fb1",
|
||||
"activity-core/docs/gitops-release.md": "b1ce725b881a4d506e1129ad330451b75d65d81a3db2a63351867710bbd89da7",
|
||||
"activity-core/docs/platform-recovery-scheduling-plan.md": "3b86a1bddae65abd56697360303ae9f56a969c98ae98d757f1ed689aaa241698"
|
||||
},
|
||||
"new_attended_items": [],
|
||||
"new_red_batch_items": [],
|
||||
"failed_clock_observation_retained": "readiness-clock-refusal.json",
|
||||
"timestamp_method": "local observer measured receipt after SSH return; remote timestamp retained separately, never used to backfill continuous health",
|
||||
"validated_at": "2026-10-11T19:47:19.802659+00:00"
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"work_record": "RPF-WP-0044-T04",
|
||||
"scope_approval_ref": "89f1058f-33bc-47a8-8a49-0b44d426c67e",
|
||||
"repository": "coulomb/rapp-issue-core",
|
||||
"read_only": true,
|
||||
"openbao_path": "platform/operators/argocd/repositories/rapp-issue-core",
|
||||
"destination": "argocd/rapp-issue-core-repository",
|
||||
"project": "railiance-tenants",
|
||||
"url": "ssh://git@forgejo.coulomb.social:30022/coulomb/rapp-issue-core.git",
|
||||
"wrap_ttl_seconds": 60,
|
||||
"new_network_scope": false,
|
||||
"owner_review_ref": null,
|
||||
"ssh_egress_existing_proof_ref": null,
|
||||
"runtime_identity_ref": null,
|
||||
"rollback_ref": null,
|
||||
"ssh_known_hosts_sha256": null,
|
||||
"expires_at": "2026-10-12T23:59:59+00:00",
|
||||
"execution_mode": "attended-founder-bounded"
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"synthetic_only": true,
|
||||
"no_actual_credentials_or_services": true,
|
||||
"installed_cli_combined_path_stdin_value_preserved": true,
|
||||
"installed_cli_cas0_body_verified": true,
|
||||
"list404_class": {
|
||||
"exit": 2,
|
||||
"stdout": "{}",
|
||||
"stderr_empty": true
|
||||
},
|
||||
"consumed_unwrap_error_class": {
|
||||
"exit": 2,
|
||||
"http_code": 400,
|
||||
"exact_error": "wrapping token is not valid or does not exist"
|
||||
},
|
||||
"help_confirmed": [
|
||||
"kv put field=- stdin semantics",
|
||||
"cas=0 create-only",
|
||||
"unwrap with no token argument uses current authenticated token"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"schema": "argocd-issue-core-fixed-command-review/v1",
|
||||
"task": "RPF-WP-0044-T04",
|
||||
"approval_ref": "89f1058f-33bc-47a8-8a49-0b44d426c67e",
|
||||
"second_founder_approval_required": false,
|
||||
"source_scope_already_approved": "Dedicated Argo reader, only coulomb/rapp-issue-core read-only, exact operator custody path",
|
||||
"command": "BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200 warden access openbao-platform-admin-login --exec -- python3 scripts/provision_argocd_issue_core_repository.py --contract <reviewed-fixed-contract.json> --contract-sha256 <exact-file-sha256> --journal <new-private-receipt.json>",
|
||||
"runtime": "Existing attended Platform-admin login encloses nested warden forgejo-admin-api-token --exec; silent outer fds suppress proxy inherited fetch errors; credential injection stays inside child. Kubectl destination is fixed SSH railiance01 native API, never workstation kubecontext.",
|
||||
"runtime_authority_limits": "Current admin lane is interim proxy, not native issuer. It requires caller OpenBao identity, and operatorpath create/read/update capability plus parent metadata list. Source plan autonomous label is not runtime proof. No exact delegated operatorpath identity found in source; absent such a proven identity the outer OIDC/MFA login is Orange. This command is explicitly attended-founder-bounded, not autopilot. Root capability is accepted only in this declared attended mode; refs are manual review attestations, not native approval tokens.",
|
||||
"preflight": [
|
||||
"exact contract references existing D2 and owner review, expiry and hash",
|
||||
"node public IP exact 92.205.62.239 before trust",
|
||||
"actual Argo SSH host CM hash matches trusted provider pin for [forgejo.coulomb.social]:30022",
|
||||
"existing SSH egress proof, no new network scope",
|
||||
"AppProject existing sourceRepos admits exact SSH repository",
|
||||
"operatorpath create/read/update capabilities and fresh custody",
|
||||
"fresh exact destination Secret and no matching provider deploykey"
|
||||
],
|
||||
"custody": "tmpfs generated ed25519 private key -> provider receives public key only -> OpenBao CAS0 exact SSH_PRIVATE_KEY -> 60-second response wrap -> exactly one unwrap with child token env -> Secret create encrypted SSH stdin, no last-applied annotation Installed Bao CLI actual synthetic HTTP fixture confirmed combined-path SSH_PRIVATE_KEY=- stdin and CAS0 request, LIST404 -> exit2 empty JSON {}, and unwrap400 specific native error.",
|
||||
"receipt": "Fixed non-secret stages, provider key ID, public-key hash, Bao write/unwrap request IDs and wrap accessor, custody version. All raw provider/CLI stdout/stderr discarded. Successful issuance says delivered_not_adopted; Argo adoption/health remains independent. Exact wrap creation path verified/retained, enabled audit-device metadata/count checked before grant, consumed-token replay must return native OpenBao 400 specific invalid-wrapping-token error; transport/403/unrelated 400 are not proof.",
|
||||
"replay": "Exclusive O_NOFOLLOW journal; any preexisting journal refuses. Pending/ambiguous stages retained uncertain_do_not_replay. Do not rerun; reconcile IDs/custody/destination first. No automatic second key, unwrap, retry, deletion or broad credential fallback.",
|
||||
"rollback": "Owner verifies journal provider key ID + exact fixed repo and public hash, revokes only that deploykey through same admin exec lane. Remove only exact matched Secret version, soft-delete only exact new operatorpath custody version, restore Application previous Git transport/revision and manual sync; never delete shared policy/store or another repo key. Revocation/removal is separate reviewed owner recovery; issuer performs no destructive fallback.",
|
||||
"limitations": [
|
||||
"No native SSH-key expiry; maximum 180-day lifecycle rotation remains owner task.",
|
||||
"No automatic rollback command executes.",
|
||||
"No actual credential read, issue, unwrap, Secret create, or adoption executed.",
|
||||
"Contract hostpin, egress, ownerreview/runtime/rollback refs must be filled and reviewed before command runs."
|
||||
],
|
||||
"checks": {
|
||||
"unit_tests": 9,
|
||||
"passed": true,
|
||||
"installed_cli_synthetic_put_stdin_cas0": true,
|
||||
"installed_cli_synthetic_list404_signature_verified": true,
|
||||
"installed_cli_synthetic_unwrap400_signature_verified": true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"schema": "argocd-public-transport/v1",
|
||||
"task": "RPF-WP-0044-T04",
|
||||
"approval_ref": "89f1058f-33bc-47a8-8a49-0b44d426c67e",
|
||||
"abort_owner": "railiance-platform with railiance-enablement",
|
||||
"diff_ref": "source-manifest.json",
|
||||
"rollback": "Run same helper --action rollback using reviewed plan; guarded exact after-to-before fields only. Revert corresponding owner source patches too.",
|
||||
"namespace": "argocd",
|
||||
"target": "railiance01",
|
||||
"baseline_sha256": "28074ce931b8362d0aab101317aef2decae69af527f5b0ee0699b5877ff89bb3",
|
||||
"public_authority_observation_sha256": "264a71f4bf8672dfb3ac5d75ed7a7f467ae17d2f6c19aaf55893ff13718e3093",
|
||||
"provider_pod_uid": "bc771b51-e9b8-456d-b59c-e7041704389d",
|
||||
"repo_server_tcp_30022_verified": true,
|
||||
"resources": [
|
||||
{
|
||||
"kind": "configmap",
|
||||
"name": "argocd-ssh-known-hosts-cm",
|
||||
"uid": "e1481984-dc5d-4fcc-9f2a-118c054865b7",
|
||||
"path": "/data/ssh_known_hosts",
|
||||
"before": "# This file was automatically generated by hack/update-ssh-known-hosts.sh. DO NOT EDIT\n[ssh.github.com]:443 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=\n[ssh.github.com]:443 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl\n[ssh.github.com]:443 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=\nbitbucket.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPIQmuzMBuKdWeF4+a2sjSSpBK0iqitSQ+5BM9KhpexuGt20JpTVM7u5BDZngncgrqDMbWdxMWWOGtZ9UgbqgZE=\nbitbucket.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIazEu89wgQZ4bqs3d63QSMzYVa0MuJ2e2gKTKqu+UUO\nbitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDQeJzhupRu0u0cdegZIa8e86EG2qOCsIsD1Xw0xSeiPDlCr7kq97NLmMbpKTX6Esc30NuoqEEHCuc7yWtwp8dI76EEEB1VqY9QJq6vk+aySyboD5QF61I/1WeTwu+deCbgKMGbUijeXhtfbxSxm6JwGrXrhBdofTsbKRUsrN1WoNgUa8uqN1Vx6WAJw1JHPhglEGGHea6QICwJOAr/6mrui/oB7pkaWKHj3z7d1IC4KWLtY47elvjbaTlkN04Kc/5LFEirorGYVbt15kAUlqGM65pk6ZBxtaO3+30LVlORZkxOh+LKL/BvbZ/iRNhItLqNyieoQj/uh/7Iv4uyH/cV/0b4WDSd3DptigWq84lJubb9t/DnZlrJazxyDCulTmKdOR7vs9gMTo+uoIrPSb8ScTtvw65+odKAlBj59dhnVp9zd7QUojOpXlL62Aw56U4oO+FALuevvMjiWeavKhJqlR7i5n9srYcrNV7ttmDw7kf/97P5zauIhxcjX+xHv4M=\ngithub.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=\ngithub.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=\ngitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY=\ngitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf\ngitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9\nssh.dev.azure.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H\nvs-ssh.visualstudio.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H\n",
|
||||
"after": "# This file was automatically generated by hack/update-ssh-known-hosts.sh. DO NOT EDIT\n[ssh.github.com]:443 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=\n[ssh.github.com]:443 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl\n[ssh.github.com]:443 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=\nbitbucket.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPIQmuzMBuKdWeF4+a2sjSSpBK0iqitSQ+5BM9KhpexuGt20JpTVM7u5BDZngncgrqDMbWdxMWWOGtZ9UgbqgZE=\nbitbucket.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIazEu89wgQZ4bqs3d63QSMzYVa0MuJ2e2gKTKqu+UUO\nbitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDQeJzhupRu0u0cdegZIa8e86EG2qOCsIsD1Xw0xSeiPDlCr7kq97NLmMbpKTX6Esc30NuoqEEHCuc7yWtwp8dI76EEEB1VqY9QJq6vk+aySyboD5QF61I/1WeTwu+deCbgKMGbUijeXhtfbxSxm6JwGrXrhBdofTsbKRUsrN1WoNgUa8uqN1Vx6WAJw1JHPhglEGGHea6QICwJOAr/6mrui/oB7pkaWKHj3z7d1IC4KWLtY47elvjbaTlkN04Kc/5LFEirorGYVbt15kAUlqGM65pk6ZBxtaO3+30LVlORZkxOh+LKL/BvbZ/iRNhItLqNyieoQj/uh/7Iv4uyH/cV/0b4WDSd3DptigWq84lJubb9t/DnZlrJazxyDCulTmKdOR7vs9gMTo+uoIrPSb8ScTtvw65+odKAlBj59dhnVp9zd7QUojOpXlL62Aw56U4oO+FALuevvMjiWeavKhJqlR7i5n9srYcrNV7ttmDw7kf/97P5zauIhxcjX+xHv4M=\ngithub.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=\ngithub.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=\ngitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY=\ngitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf\ngitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9\nssh.dev.azure.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H\nvs-ssh.visualstudio.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H\n[forgejo.coulomb.social]:30022 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC7jadWwHX2j1aeXSonkdwP8CI5j1a+ihzj6kq7697h+8B5X2SpYBvwOi2brYmgoiccyapB2aowmctMLLTtrhMaARtvnZPeoFgPhBrdHaUhHdmTeSg7MbRBu5D8yYTt3CwDxF1+MUFE+NuRhCXRHQLbLCqTLlU9hgZV4xuqsv71lvNHzcF3vMcolTBtdVzTsgCfeV9J8wQMXTC7rFfVuRi5fC6m994KN+vt94VHIZcZwFgjdCEZ4uo7U07Fdp3MT6WMqgLftc9ewBL/p+i5BtzVkF725d5QMx6TL6vLrC7X8lrkUWQL+oy5CeUrnOnsyiuwbYwlFVLsdypa2WbdlHtkllBXVD2IdTxeu038BAjWBXkN1WCGEXWJvj829I8FzObnO+QoOsbwdmGSTFqBX7+2vSymbEHtNcS0/0jk5xUvdnvFRvkzDvhQjMgiF8LIVCk4zWTBeWPQ5r5lLooFn8B1hmsgqHVSd2DXMPUMOuTQRTdxpNicNkBlwEQSwFYSCf3vL+x2e2pIx4YvkLD+1bEwNwB8HIuzF1Vex7Cjbo07pTwxhTJ52ZFQNkja/LqyAzO0bOEb9MF6KIxXKS/qL5C2cf8kHB5eXbiAwbv3jkxVzBiYPEMJYxO0pJKaKK8Cvs8cT2MYsxQkly1MENsvf15eOHqg26CPz/PiruHkiifhXw==\n",
|
||||
"owner": "railiance-enablement"
|
||||
},
|
||||
{
|
||||
"kind": "appproject",
|
||||
"name": "railiance-tenants",
|
||||
"uid": "f899f3ec-2c55-4cd0-9cb0-b2b4c6eb9be2",
|
||||
"path": "/spec/sourceRepos",
|
||||
"before": [
|
||||
"https://forgejo.coulomb.social/coulomb/*.git",
|
||||
"https://gitea.coulomb.social/coulomb/*.git"
|
||||
],
|
||||
"after": [
|
||||
"https://forgejo.coulomb.social/coulomb/*.git",
|
||||
"https://gitea.coulomb.social/coulomb/*.git",
|
||||
"ssh://git@forgejo.coulomb.social:30022/coulomb/rapp-issue-core.git"
|
||||
],
|
||||
"owner": "railiance-platform"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
{
|
||||
"application": {
|
||||
"automated_present": false,
|
||||
"condition_types": [
|
||||
"OrphanedResourceWarning"
|
||||
],
|
||||
"destination": {
|
||||
"namespace": "target-revenue",
|
||||
"server": "https://kubernetes.default.svc"
|
||||
},
|
||||
"health": "Healthy",
|
||||
"name": "target-revenue",
|
||||
"operation": {
|
||||
"finishedAt": "2026-09-28T14:23:27Z",
|
||||
"phase": "Succeeded",
|
||||
"startedAt": "2026-09-28T14:23:26Z"
|
||||
},
|
||||
"operation_revision": "a3a8a27a8cda32ae3c7b55353ee16a131c50a0a0",
|
||||
"pending_operation": false,
|
||||
"project": "railiance-tenants",
|
||||
"resources": [
|
||||
{
|
||||
"group": null,
|
||||
"kind": "Service",
|
||||
"name": "target-revenue",
|
||||
"namespace": "target-revenue",
|
||||
"status": "Synced"
|
||||
},
|
||||
{
|
||||
"group": "apps",
|
||||
"kind": "Deployment",
|
||||
"name": "target-revenue",
|
||||
"namespace": "target-revenue",
|
||||
"status": "Synced"
|
||||
},
|
||||
{
|
||||
"group": "external-secrets.io",
|
||||
"kind": "ExternalSecret",
|
||||
"name": "target-revenue-runtime",
|
||||
"namespace": "target-revenue",
|
||||
"status": "Synced"
|
||||
},
|
||||
{
|
||||
"group": "networking.k8s.io",
|
||||
"kind": "Ingress",
|
||||
"name": "target-revenue",
|
||||
"namespace": "target-revenue",
|
||||
"status": "Synced"
|
||||
},
|
||||
{
|
||||
"group": "postgresql.cnpg.io",
|
||||
"kind": "Cluster",
|
||||
"name": "target-revenue-pg",
|
||||
"namespace": "target-revenue",
|
||||
"status": "Synced"
|
||||
}
|
||||
],
|
||||
"revision": "a3a8a27a8cda32ae3c7b55353ee16a131c50a0a0",
|
||||
"source": {
|
||||
"path": "k8s/railiance",
|
||||
"repoURL": "https://forgejo.coulomb.social/coulomb/target-revenue.git",
|
||||
"targetRevision": "a3a8a27a8cda32ae3c7b55353ee16a131c50a0a0"
|
||||
},
|
||||
"sync": "Synced",
|
||||
"sync_prune": false,
|
||||
"sync_strategy_apply": true,
|
||||
"sync_strategy_force": false,
|
||||
"uid": "42285c08-97c6-452f-ad8a-4f46d9691bc9"
|
||||
},
|
||||
"captured_at": "2026-10-11T19:51:12.812423+00:00",
|
||||
"estate_ip": "92.205.62.239",
|
||||
"live_hooks": [],
|
||||
"pinned_render_diff": {
|
||||
"client_side_exit_code": 0,
|
||||
"normalized_render_sha256": "3d08814b968978d634f4ecb444db31d24ff19d15ae12d8411dacb6e1448859ff",
|
||||
"output_suppressed": true,
|
||||
"path": "k8s/railiance",
|
||||
"render_resource_count": 5,
|
||||
"render_sha256": "c728d28ac2b7fd61be00e65d356d53a6b729573fdd5a15d420efb86b7c89f50a",
|
||||
"revision": "a3a8a27a8cda32ae3c7b55353ee16a131c50a0a0",
|
||||
"skipped_hooks": [
|
||||
"target-revenue-bootstrap-binky",
|
||||
"target-revenue-migrate"
|
||||
]
|
||||
},
|
||||
"ready_checks": [
|
||||
{
|
||||
"available_replicas": 1,
|
||||
"condition": "Available",
|
||||
"generation": 6,
|
||||
"instances": null,
|
||||
"kind": "Deployment",
|
||||
"name": "target-revenue",
|
||||
"namespace": "target-revenue",
|
||||
"observed_generation": 6,
|
||||
"ready": [
|
||||
{
|
||||
"lastTransitionTime": "2026-08-15T22:30:36Z",
|
||||
"status": "True"
|
||||
}
|
||||
],
|
||||
"ready_instances": null,
|
||||
"replicas": 1,
|
||||
"tracking_matches": true,
|
||||
"uid": "c6b7a312-968a-4d10-894d-d4f6b7b11205"
|
||||
},
|
||||
{
|
||||
"available_replicas": null,
|
||||
"condition": "Ready",
|
||||
"generation": 2,
|
||||
"instances": null,
|
||||
"kind": "ExternalSecret",
|
||||
"name": "target-revenue-runtime",
|
||||
"namespace": "target-revenue",
|
||||
"observed_generation": null,
|
||||
"ready": [
|
||||
{
|
||||
"lastTransitionTime": "2026-09-05T16:39:12Z",
|
||||
"status": "True"
|
||||
}
|
||||
],
|
||||
"ready_instances": null,
|
||||
"replicas": null,
|
||||
"tracking_matches": true,
|
||||
"uid": "cde6e839-a964-443d-a19d-32cc5b53a9cb"
|
||||
},
|
||||
{
|
||||
"available_replicas": null,
|
||||
"condition": "Ready",
|
||||
"generation": 1,
|
||||
"instances": 1,
|
||||
"kind": "Cluster",
|
||||
"name": "target-revenue-pg",
|
||||
"namespace": "target-revenue",
|
||||
"observed_generation": null,
|
||||
"ready": [
|
||||
{
|
||||
"lastTransitionTime": "2026-08-05T15:51:45Z",
|
||||
"status": "True"
|
||||
}
|
||||
],
|
||||
"ready_instances": 1,
|
||||
"replicas": null,
|
||||
"tracking_matches": true,
|
||||
"uid": "333e9409-0a9c-47fa-bb73-6c7309f0a099"
|
||||
}
|
||||
],
|
||||
"tracking_settings": {
|
||||
"controller_version": "v3.5.3",
|
||||
"installation_id_absent": true,
|
||||
"instance_label_key": "app.kubernetes.io/instance",
|
||||
"method": "annotation"
|
||||
},
|
||||
"public_health": {
|
||||
"url": "https://revenue.coulomb.social/healthz",
|
||||
"status": 200
|
||||
},
|
||||
"metadata_captured_at": "2026-10-11T19:51:13.501350+00:00"
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"schema_version": "argocd-workload-adoption-proof/v1",
|
||||
"status": "verified",
|
||||
"application": "target-revenue",
|
||||
"revision": "a3a8a27a8cda32ae3c7b55353ee16a131c50a0a0",
|
||||
"captured_at": "2026-10-11T19:51:12.812423+00:00",
|
||||
"verified_at": "2026-10-11T19:51:12.813305+00:00",
|
||||
"proving_seconds": 1142865.812423,
|
||||
"resource_count": 5,
|
||||
"ready_count": 3,
|
||||
"manual_apply_prune_off": true,
|
||||
"hooks_skipped": [
|
||||
"target-revenue-bootstrap-binky",
|
||||
"target-revenue-migrate"
|
||||
],
|
||||
"hooks_absent": true,
|
||||
"child_automation_enabled": false,
|
||||
"public_health_status": 200,
|
||||
"native_mutation": false,
|
||||
"verifier_sha256": "cf5d2113425d3e014f937371af31d9bcb11671ea036c1b8b8a65aaac47f954b4",
|
||||
"tracking_helper_sha256": "c058c0ea646bcc3410e0d26db6b42a5471a8fa45a1df62b9ec85f2bf7d03f660",
|
||||
"contract_sha256": "9946a3f3b5b7612ba9d6352b4dc795f2e5e0385d7d296cc4bf14411e1d7805dd",
|
||||
"observation_sha256": "d77a033160e62b75f9f7df384222f8cd11ea6e8303f02e83909b4f25d201d1dd"
|
||||
}
|
||||
210
scripts/activity_promotion_readiness.py
Normal file
210
scripts/activity_promotion_readiness.py
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Read-only activity-core promotion gate; signed owner acceptance is mandatory.
|
||||
|
||||
D2 grants scoped activation after proof, never substitutes for release-principal
|
||||
custody, authenticated issuers or rollback. This checker cannot activate a worker,
|
||||
issue credentials, change Argo policy or admit its own caller as a trusted signer.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import argparse
|
||||
import base64
|
||||
from datetime import datetime, timezone, timedelta
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
PIN = 'a86a14332d76d92a7ed176ea34dca4429902c396'
|
||||
OWNER_TASK = 'ACTIVITY-WP-0041-T03'
|
||||
AUTHORITY = 'ACTIVITY-WP-0041-image-only-v1'
|
||||
APPLICATION_PATH = 'argocd/railiance01/applications/activity-core.application.yaml'
|
||||
RESOURCES = {
|
||||
('', 'ConfigMap', 'activity-core', name) for name in
|
||||
('actcore-runtime-config', 'actcore-external-activity-definitions', 'actcore-report-schemas', 'actcore-ops-service-inventory')
|
||||
} | {('', 'Service', 'activity-core', name) for name in ('actcore-api', 'actcore-worker-metrics')} | {
|
||||
('apps', 'Deployment', 'activity-core', name) for name in ('actcore-api', 'actcore-worker', 'actcore-event-router')
|
||||
}
|
||||
CHECKS = {
|
||||
'principal_scope_enforced', 'credential_custody_verified', 'revocation_denial_proven',
|
||||
'independent_receipt_issuers_verified', 'authenticated_ci_digest_binding_proven',
|
||||
'continuous_health_observer_active', 'live_and_rollback_retention_verified',
|
||||
'image_only_admission_enforced', 'source_cas_and_single_flight_proven',
|
||||
'restart_and_lost_response_recovery_proven', 'out_of_scope_denials_proven',
|
||||
'forged_stale_mismatched_receipt_denials_proven', 'failed_health_rollback_proven',
|
||||
'temporal_dispatch_registered', 'durable_audit_sink_verified', 'report_schedule_invariants_verified',
|
||||
}
|
||||
TARGETS = {
|
||||
'platform_repository': 'coulomb/railiance-platform', 'branch': 'main',
|
||||
'child_path': APPLICATION_PATH, 'child_field': '/spec/source/targetRevision',
|
||||
'root_application': 'railiance-apps-root', 'root_sync_selects_only': 'activity-core',
|
||||
'child_application': 'activity-core', 'prune': False,
|
||||
}
|
||||
|
||||
|
||||
def canonical(value):
|
||||
return json.dumps(value, sort_keys=True, separators=(',', ':'), allow_nan=False).encode()
|
||||
|
||||
|
||||
def stamp(value):
|
||||
result = datetime.fromisoformat(value.replace('Z', '+00:00'))
|
||||
if result.tzinfo is None:
|
||||
raise ValueError('timezone required')
|
||||
return result
|
||||
|
||||
|
||||
def run(argv, *, input=None):
|
||||
try:
|
||||
result = subprocess.run(argv, input=input, capture_output=True, timeout=100)
|
||||
if result.returncode:
|
||||
raise ValueError('metadata read unverified; protected output suppressed')
|
||||
return result.stdout
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
raise ValueError('metadata read unverified; protected output suppressed') from None
|
||||
|
||||
|
||||
def collect():
|
||||
def get(args):
|
||||
return json.loads(run(['kubectl', *args, '-o', 'json']))
|
||||
nodes = get(['get', 'nodes'])
|
||||
if '92.205.62.239' not in [a['address'] for n in nodes['items'] for a in n['status']['addresses']]:
|
||||
raise ValueError('estate identity mismatch')
|
||||
namespace = get(['get', 'namespace', 'kube-system'])
|
||||
obj = get(['-n', 'argocd', 'get', 'application', 'activity-core'])
|
||||
spec, status = obj['spec'], obj.get('status', {})
|
||||
expected_source = {'repoURL': 'https://forgejo.coulomb.social/coulomb/activity-core.git',
|
||||
'path': 'k8s/gitops', 'targetRevision': PIN}
|
||||
if spec.get('source') != expected_source:
|
||||
raise ValueError('exact activity source unverified')
|
||||
op = status.get('operationState', {})
|
||||
deployments = []
|
||||
for name in ('actcore-api', 'actcore-worker', 'actcore-event-router'):
|
||||
deployment = get(['-n', 'activity-core', 'get', 'deployment', name])
|
||||
generation = deployment['metadata']['generation']; ds = deployment.get('status', {})
|
||||
desired = deployment['spec'].get('replicas', 1)
|
||||
deployments.append({'name': name, 'generation_observed': ds.get('observedGeneration') == generation,
|
||||
'replicas': desired, 'ready': ds.get('readyReplicas', 0),
|
||||
'updated': ds.get('updatedReplicas', 0), 'available': ds.get('availableReplicas', 0)})
|
||||
project = get(['-n', 'argocd', 'get', 'appproject', 'activity-core'])['spec']
|
||||
return {'captured_at': datetime.now(timezone.utc).isoformat(), 'estate_ip': '92.205.62.239',
|
||||
'cluster_uid': namespace['metadata']['uid'], 'application_uid': obj['metadata']['uid'],
|
||||
'source': expected_source, 'project': spec.get('project'),
|
||||
'destination': spec.get('destination'), 'automated_present': 'automated' in spec.get('syncPolicy', {}),
|
||||
'sync_options': spec.get('syncPolicy', {}).get('syncOptions', []),
|
||||
'pending_operation': bool(obj.get('operation')), 'sync': status.get('sync', {}).get('status'),
|
||||
'revision': status.get('sync', {}).get('revision'), 'health': status.get('health', {}).get('status'),
|
||||
'operation': {k: op.get(k) for k in ('phase', 'finishedAt')},
|
||||
'condition_types': [c.get('type') for c in status.get('conditions', [])],
|
||||
'resources': [{k: r.get(k) for k in ('group', 'kind', 'namespace', 'name', 'status')} for r in status.get('resources', [])],
|
||||
'project_scope_matches': (project.get('sourceRepos') == ['https://forgejo.coulomb.social/coulomb/activity-core.git']
|
||||
and project.get('destinations') == [{'server': 'https://kubernetes.default.svc', 'namespace': 'activity-core'}]
|
||||
and project.get('clusterResourceWhitelist', []) == []
|
||||
and {(r.get('group') or '',r['kind']) for r in project.get('namespaceResourceWhitelist', [])}
|
||||
== {('', 'ConfigMap'),('', 'Service'),('apps','Deployment')}),
|
||||
'deployments': deployments}
|
||||
|
||||
|
||||
def adoption_checks(observation, now):
|
||||
if (not 0 <= (now-stamp(observation['captured_at'])).total_seconds() <= 300
|
||||
or observation['estate_ip'] != '92.205.62.239' or not observation['cluster_uid']
|
||||
or not observation['application_uid']):
|
||||
raise ValueError('fresh exact estate observation required')
|
||||
if (observation['source'] != {'repoURL':'https://forgejo.coulomb.social/coulomb/activity-core.git','path':'k8s/gitops','targetRevision':PIN}
|
||||
or observation['project'] != 'activity-core' or observation['revision'] != PIN
|
||||
or observation['destination'] != {'server':'https://kubernetes.default.svc','namespace':'activity-core'}
|
||||
or observation['automated_present'] is not False or observation['pending_operation'] is not False
|
||||
or set(observation['sync_options']) != {'CreateNamespace=false','ApplyOutOfSyncOnly=true','PruneLast=true','FailOnSharedResource=true'}
|
||||
or observation['sync'] != 'Synced' or observation['health'] != 'Healthy'
|
||||
or observation['condition_types'] or observation['project_scope_matches'] is not True):
|
||||
raise ValueError('bounded manual adoption scope unverified')
|
||||
op = observation['operation']
|
||||
if op['phase'] != 'Succeeded' or (stamp(observation['captured_at'])-stamp(op['finishedAt'])).total_seconds() < 86400:
|
||||
raise ValueError('24-hour adoption eligibility unverified')
|
||||
resources = observation['resources']
|
||||
if (len(resources) != 9 or {tuple(r.get(k) or '' for k in ('group','kind','namespace','name')) for r in resources} != RESOURCES
|
||||
or any(r['status'] != 'Synced' for r in resources)):
|
||||
raise ValueError('nine-resource runtime inventory unverified')
|
||||
deployments = observation['deployments']
|
||||
if (len(deployments) != 3 or {d['name'] for d in deployments} != {'actcore-api','actcore-worker','actcore-event-router'}
|
||||
or any(d['generation_observed'] is not True or type(d['replicas']) is not int or d['replicas'] < 1
|
||||
or any(d[k] != d['replicas'] for k in ('ready','updated','available')) for d in deployments)):
|
||||
raise ValueError('deployment generation/readiness unverified')
|
||||
|
||||
|
||||
def owner_acceptance(envelope, trusted_key, trusted_principal, observation, now):
|
||||
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
|
||||
if set(envelope) != {'payload','signature'} or not trusted_principal or len(trusted_key) != 32:
|
||||
raise ValueError('trusted owner acceptance configuration required')
|
||||
payload = envelope['payload']
|
||||
Ed25519PublicKey.from_public_bytes(trusted_key).verify(base64.b64decode(envelope['signature'],validate=True), canonical(payload))
|
||||
if (payload.get('schema_version') != 'activity-core-release-acceptance/v1'
|
||||
or payload.get('owner_task') != OWNER_TASK or payload.get('issuer') != trusted_principal
|
||||
or payload.get('authority') != AUTHORITY or payload.get('application') != 'activity-core'
|
||||
or payload.get('cluster_uid') != observation['cluster_uid'] or payload.get('adoption_revision') != PIN
|
||||
or payload.get('targets') != TARGETS or not payload.get('release_principal')
|
||||
or not re.fullmatch('[a-f0-9]{40}',payload.get('broker_revision',''))
|
||||
or set(payload.get('checks',{})) != CHECKS
|
||||
or any(value is not True for value in payload['checks'].values())):
|
||||
raise ValueError('owner acceptance scope or machine proof incomplete')
|
||||
issued, expires = stamp(payload['issued_at']), stamp(payload['expires_at'])
|
||||
if not 0 <= (now-issued).total_seconds() <= 300 or not now < expires <= issued + timedelta(minutes=5):
|
||||
raise ValueError('owner acceptance stale or expired')
|
||||
health = payload['continuous_health']
|
||||
if (health.get('revision') != PIN or health.get('continuous') is not True
|
||||
or type(health.get('max_gap_seconds')) is not int or not 0 < health['max_gap_seconds'] <= 90
|
||||
or not 0 <= (now-stamp(health['observed_at'])).total_seconds() <= 90
|
||||
or (stamp(health['observed_at'])-stamp(health['healthy_since'])).total_seconds() < 86400):
|
||||
raise ValueError('owner continuous observation proof incomplete')
|
||||
return payload
|
||||
|
||||
|
||||
def readiness(observation, *, envelope=None, trusted_key=None, trusted_principal=None, now=None):
|
||||
now = now or datetime.now(timezone.utc)
|
||||
base = {'schema_version':'activity-promotion-readiness/v1','task':'RPF-WP-0048-T02',
|
||||
'owner_dependency':OWNER_TASK,'native_mutation':False,'automatic_sync_candidate':False,
|
||||
'observed_at':now.isoformat(),'checker_sha256':hashlib.sha256(Path(__file__).read_bytes()).hexdigest()}
|
||||
try:
|
||||
adoption_checks(observation,now)
|
||||
except Exception:
|
||||
return base | {'status':'unverified','reason':'adoption_metadata_unverified'}
|
||||
base |= {'adoption_24h_eligible':True,'adoption_snapshot_is_not_continuous_observation':True,
|
||||
'observation_sha256':hashlib.sha256(canonical(observation)).hexdigest()}
|
||||
if envelope is None or trusted_key is None or trusted_principal is None:
|
||||
return base | {'status':'waiting','reason':'authenticated_owner_activation_acceptance_missing',
|
||||
'depends_on':[OWNER_TASK],'needs_human':False}
|
||||
try:
|
||||
payload = owner_acceptance(envelope,trusted_key,trusted_principal,observation,now)
|
||||
except Exception:
|
||||
return base | {'status':'unverified','reason':'owner_activation_acceptance_unverified'}
|
||||
return base | {'status':'ready','owner_acceptance_sha256':hashlib.sha256(canonical(envelope)).hexdigest(),
|
||||
'release_principal':payload['release_principal'],'broker_revision':payload['broker_revision'],
|
||||
'scope':AUTHORITY,'rollback':'disable dedicated release dispatcher admission; retain manual child policy and prior source pin',
|
||||
'abort_owner':'railiance-platform'}
|
||||
|
||||
|
||||
def main():
|
||||
parser=argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--live',action='store_true');parser.add_argument('--remote',action='store_true',help=argparse.SUPPRESS)
|
||||
parser.add_argument('--observation',type=Path);parser.add_argument('--receipt',type=Path)
|
||||
parser.add_argument('--owner-acceptance',type=Path)
|
||||
parser.add_argument('--trusted-owner-key',type=Path,help='operator-owned raw Ed25519 public key; never receipt-provided')
|
||||
parser.add_argument('--trusted-owner-principal',help='operator-owned issuer binding')
|
||||
args=parser.parse_args()
|
||||
try:
|
||||
if args.remote:
|
||||
print(json.dumps(collect(),sort_keys=True));return 0
|
||||
if args.live:
|
||||
observation=json.loads(run(['ssh','railiance01','python3','-','--remote'],input=Path(__file__).read_bytes()))
|
||||
observation['remote_captured_at']=observation['captured_at']
|
||||
observation['captured_at']=datetime.now(timezone.utc).isoformat()
|
||||
if args.observation:args.observation.write_text(json.dumps(observation,indent=2)+'\n')
|
||||
else:observation=json.loads(args.observation.read_text())
|
||||
envelope=json.loads(args.owner_acceptance.read_text()) if args.owner_acceptance else None
|
||||
key=args.trusted_owner_key.read_bytes() if args.trusted_owner_key else None
|
||||
result=readiness(observation,envelope=envelope,trusted_key=key,trusted_principal=args.trusted_owner_principal)
|
||||
except Exception:result={'schema_version':'activity-promotion-readiness/v1','status':'unverified','reason':'protected_read_or_inputs_unverified','native_mutation':False}
|
||||
if args.receipt:args.receipt.write_text(json.dumps(result,indent=2)+'\n')
|
||||
print(json.dumps(result,sort_keys=True));return 0 if result['status']=='ready' else 1
|
||||
|
||||
|
||||
if __name__=='__main__':raise SystemExit(main())
|
||||
121
scripts/argocd_public_transport.py
Normal file
121
scripts/argocd_public_transport.py
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Guarded public-only Argo transport prerequisite; no Secret or credential calls."""
|
||||
import argparse
|
||||
import copy
|
||||
from datetime import datetime,timezone
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
URL='ssh://git@forgejo.coulomb.social:30022/coulomb/rapp-issue-core.git'
|
||||
D2='89f1058f-33bc-47a8-8a49-0b44d426c67e'
|
||||
TARGETS=[('configmap','argocd-ssh-known-hosts-cm','/data/ssh_known_hosts','railiance-enablement'),('appproject','railiance-tenants','/spec/sourceRepos','railiance-platform')]
|
||||
|
||||
class Refused(Exception):pass
|
||||
|
||||
def require(ok,reason):
|
||||
if not ok:raise Refused(reason)
|
||||
|
||||
def digest(value):return hashlib.sha256(json.dumps(value,sort_keys=True).encode()).hexdigest()
|
||||
|
||||
def run(args,body=None):
|
||||
try:
|
||||
result=subprocess.run(['ssh','-o','BatchMode=yes','-o','ConnectTimeout=10','railiance01',shlex.join(['kubectl','--request-timeout=15s',*args])],input=body,text=True,capture_output=True,timeout=60)
|
||||
except Exception:raise Refused('command_outcome_unverified') from None
|
||||
require(result.returncode==0,'command_outcome_unverified')
|
||||
return result.stdout
|
||||
|
||||
def value(obj,path):
|
||||
for key in path.strip('/').split('/'):obj=obj[key]
|
||||
return obj
|
||||
|
||||
def replace(obj,path,wanted):
|
||||
keys=path.strip('/').split('/');parent=obj
|
||||
for key in keys[:-1]:parent=parent[key]
|
||||
parent[keys[-1]]=wanted
|
||||
|
||||
def normalized(obj):
|
||||
obj=copy.deepcopy(obj)
|
||||
for field in ('managedFields','resourceVersion','generation'):obj.get('metadata',{}).pop(field,None)
|
||||
return obj
|
||||
|
||||
def validate(plan):
|
||||
require(plan.get('schema')=='argocd-public-transport/v1' and plan.get('task')=='RPF-WP-0044-T04' and plan.get('approval_ref')==D2,'plan_authority')
|
||||
require(plan.get('target')=='railiance01' and plan.get('namespace')=='argocd' and plan.get('repo_server_tcp_30022_verified') is True,'plan_target')
|
||||
require(all(isinstance(plan.get(k),str) and plan[k] for k in ('diff_ref','rollback','abort_owner','baseline_sha256','public_authority_observation_sha256')),'plan_receipt_contract')
|
||||
require(len(plan.get('resources',[]))==2,'target_count')
|
||||
for item,fixed in zip(plan['resources'],TARGETS):
|
||||
require(tuple(item.get(k) for k in ('kind','name','path','owner'))==fixed,'fixed_target')
|
||||
require(bool(re.fullmatch(r'[a-f0-9-]{36}',item.get('uid',''))),'target_uid')
|
||||
cm,project=plan['resources']
|
||||
require(isinstance(cm['before'],str) and cm['before'].endswith('\n') and isinstance(cm['after'],str),'host_field_type')
|
||||
added=cm['after'][len(cm['before']):]
|
||||
require(cm['after'].startswith(cm['before']) and bool(re.fullmatch(r'\[forgejo\.coulomb\.social\]:30022 (ssh-rsa|ssh-ed25519|ecdsa-sha2-nistp256) [A-Za-z0-9+/=]+\n',added)),'host_addition_scope')
|
||||
require('[forgejo.coulomb.social]:30022 ' not in cm['before'],'host_preimage_already_admitted')
|
||||
require(isinstance(project['before'],list) and all(isinstance(v,str) for v in project['before']) and URL not in project['before'] and project['after']==project['before']+[URL],'repository_addition_scope')
|
||||
|
||||
def patch(item,live,rollback=False):
|
||||
before,after=(item['after'],item['before']) if rollback else (item['before'],item['after'])
|
||||
require(live['metadata']['uid']==item['uid'],'target_uid_changed')
|
||||
require(value(live,item['path'])==before,'target_preimage_changed')
|
||||
require(isinstance(live['metadata'].get('resourceVersion'),str),'resource_version_required')
|
||||
return [{'op':'test','path':'/metadata/uid','value':item['uid']},{'op':'test','path':'/metadata/resourceVersion','value':live['metadata']['resourceVersion']},{'op':'test','path':item['path'],'value':before},{'op':'replace','path':item['path'],'value':after}]
|
||||
|
||||
def perform(plan,action,receipt,checkpoint=lambda:None):
|
||||
validate(plan)
|
||||
nodes=json.loads(run(['get','nodes','-o','json']))['items']
|
||||
require(len(nodes)==1 and any(a.get('address')=='92.205.62.239' for a in nodes[0]['status']['addresses']),'wrong_estate')
|
||||
read=lambda item:json.loads(run(['-n','argocd','get',item['kind'],item['name'],'-o','json']))
|
||||
rollback=action=='rollback'
|
||||
# Validate both targets before the first write, avoiding an avoidable partial change.
|
||||
live=[read(item) for item in plan['resources']]
|
||||
for item,obj in zip(plan['resources'],live):
|
||||
require(obj['metadata']['uid']==item['uid'],'target_uid_changed')
|
||||
if action=='verify':require(value(obj,item['path'])==item['after'],'verification_field_changed')
|
||||
elif rollback and value(obj,item['path'])==item['before']:continue
|
||||
else:patch(item,obj,rollback)
|
||||
for item,obj in zip(plan['resources'],live):
|
||||
record={'kind':item['kind'],'name':item['name'],'uid':item['uid'],'field':item['path'],'before_sha256':digest(item['before']),'after_sha256':digest(item['after'])};receipt['resources'].append(record)
|
||||
if action=='verify':record['verified']=True;continue
|
||||
if rollback and value(obj,item['path'])==item['before']:record['already_restored']=True;continue
|
||||
body=json.dumps(patch(item,obj,rollback));args=['-n','argocd','patch',item['kind'],item['name'],'--type=json','--patch-file=/dev/stdin']
|
||||
admitted=json.loads(run(args+['--dry-run=server','-o','json'],body))
|
||||
wanted=copy.deepcopy(obj);replace(wanted,item['path'],item['before'] if rollback else item['after'])
|
||||
require(normalized(admitted)==normalized(wanted),'dry_run_modified_other_fields');record['server_dry_run']=True
|
||||
if action=='dry-run':continue
|
||||
receipt['pending_target']=item['kind']+'/'+item['name'];receipt['mutation_attempted']=True;checkpoint()
|
||||
run(args+['-o','name'],body)
|
||||
checked=read(item)
|
||||
require(checked['metadata']['uid']==item['uid'] and normalized(checked)==normalized(wanted),'postchange_projection_unverified')
|
||||
record['changed_verified']=True;receipt['pending_target']=None;checkpoint()
|
||||
receipt['passed']=True;return receipt
|
||||
|
||||
def main():
|
||||
parser=argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--plan',type=Path,required=True);parser.add_argument('--plan-sha256',required=True)
|
||||
parser.add_argument('--action',choices=('dry-run','execute','verify','rollback'),default='dry-run')
|
||||
parser.add_argument('--receipt',type=Path,required=True)
|
||||
args=parser.parse_args()
|
||||
receipt={'schema':'argocd-public-transport-receipt/v1','task':'RPF-WP-0044-T04','approval_ref':D2,'action':args.action,'passed':False,'mutation_attempted':False,'resources':[],'secret_or_credential_calls':False,'captured_at':datetime.now(timezone.utc).isoformat()}
|
||||
# Exclusive receipt is a replay guard. Never overwrite another attempt.
|
||||
fd=None
|
||||
try:
|
||||
require(not args.receipt.exists(),'receipt_exists_do_not_replay')
|
||||
fd=os.open(args.receipt,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600)
|
||||
raw=args.plan.read_bytes();require(hashlib.sha256(raw).hexdigest()==args.plan_sha256,'plan_changed')
|
||||
def checkpoint():
|
||||
os.lseek(fd,0,os.SEEK_SET);os.ftruncate(fd,0);os.write(fd,(json.dumps(receipt,sort_keys=True)+'\n').encode());os.fsync(fd)
|
||||
receipt['plan_sha256']=args.plan_sha256;checkpoint();perform(json.loads(raw),args.action,receipt,checkpoint)
|
||||
except Refused as error:receipt['failure']=str(error)
|
||||
except Exception:receipt['failure']='internal_outcome_unverified'
|
||||
finally:
|
||||
if fd is not None:
|
||||
os.lseek(fd,0,os.SEEK_SET);os.ftruncate(fd,0);os.write(fd,(json.dumps(receipt,sort_keys=True)+'\n').encode());os.fsync(fd);os.close(fd)
|
||||
# Only whitelist receipt fields are emitted. Every raw child error stays captured.
|
||||
print(json.dumps(receipt,sort_keys=True));return 0 if receipt['passed'] else 1
|
||||
|
||||
if __name__=='__main__':raise SystemExit(main())
|
||||
198
scripts/provision_argocd_issue_core_repository.py
Normal file
198
scripts/provision_argocd_issue_core_repository.py
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Silent fixed-scope D2 repository deploy-key issuer/destination actuator.
|
||||
|
||||
Run through the existing Platform attended login and Forgejo admin --exec lane.
|
||||
No lane grants itself authority. The reviewed contract seals transport/host trust
|
||||
and existing egress; the command performs no RBAC, policy or network expansion.
|
||||
No implicit recovery: an existing journal, ambiguous result or drift stops replay.
|
||||
"""
|
||||
import argparse
|
||||
import base64
|
||||
from datetime import datetime, timezone
|
||||
import hashlib
|
||||
import fnmatch
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
import stat
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
REPO='coulomb/rapp-issue-core'
|
||||
BAO='platform/operators/argocd/repositories/rapp-issue-core'
|
||||
URL='ssh://git@forgejo.coulomb.social:30022/coulomb/rapp-issue-core.git'
|
||||
SECRET='rapp-issue-core-repository'
|
||||
TITLE='argocd-rapp-issue-core-readonly-D2'
|
||||
D2='89f1058f-33bc-47a8-8a49-0b44d426c67e'
|
||||
FIXED={'work_record':'RPF-WP-0044-T04','scope_approval_ref':D2,'repository':REPO,'read_only':True,'openbao_path':BAO,'destination':'argocd/'+SECRET,'project':'railiance-tenants','url':URL,'wrap_ttl_seconds':60,'new_network_scope':False,'execution_mode':'attended-founder-bounded'}
|
||||
|
||||
|
||||
class Refused(Exception):pass
|
||||
|
||||
|
||||
def require(ok,reason):
|
||||
if not ok:raise Refused(reason)
|
||||
|
||||
|
||||
def captured(argv,body=None,env=None):
|
||||
try:
|
||||
result=subprocess.run(argv,input=body,env=env,capture_output=True,text=True,timeout=60)
|
||||
except Exception:
|
||||
raise Refused('command_unverified') from None
|
||||
return result
|
||||
|
||||
|
||||
def run(argv,body=None,env=None):
|
||||
result=captured(argv,body,env)
|
||||
require(result.returncode==0,'command_unverified')
|
||||
return result.stdout
|
||||
|
||||
|
||||
def metadata_children():
|
||||
result=captured(['bao','list','-format=json','platform/metadata/operators/argocd/repositories/'])
|
||||
# Installed CLI represents an empty/404 LIST as exit 2, JSON {}, no stderr.
|
||||
if result.returncode==2 and result.stdout.strip()=='{}' and not result.stderr.strip():return []
|
||||
require(result.returncode==0,'custody_metadata_unverified')
|
||||
children=json.loads(result.stdout);require(isinstance(children,list),'custody_metadata_unverified');return children
|
||||
|
||||
|
||||
def replay_denied(env):
|
||||
result=captured(['bao','unwrap','-format=json'],env=env)
|
||||
text=result.stderr or ''
|
||||
return result.returncode==2 and not result.stdout.strip() and bool(re.search(r'Code:\s*400\b',text)) and bool(re.search(r'^\* wrapping token is not valid or does not exist\s*$',text,re.M))
|
||||
|
||||
|
||||
class NoRedirect(urllib.request.HTTPRedirectHandler):
|
||||
def redirect_request(self,*args,**kwargs):raise Refused('redirect_refused')
|
||||
|
||||
|
||||
def api(token,method,path,payload=None,expected=(200,)):
|
||||
require(path.startswith('/repos/'+REPO+'/') or path=='/repos/'+REPO,'provider_path_refused')
|
||||
request=urllib.request.Request('https://forgejo.coulomb.social/api/v1'+path,method=method,
|
||||
data=None if payload is None else json.dumps(payload).encode(),
|
||||
headers={'Authorization':'token '+token,'Content-Type':'application/json'})
|
||||
try:
|
||||
with urllib.request.build_opener(NoRedirect()).open(request,timeout=30) as response:
|
||||
require(response.status in expected,'provider_status_refused')
|
||||
raw=response.read(1024*1024+1);require(len(raw)<=1024*1024,'provider_body_bound')
|
||||
return json.loads(raw) if raw else None
|
||||
except Refused:raise
|
||||
except Exception:raise Refused('provider_outcome_unverified') from None
|
||||
|
||||
|
||||
def validate_contract(contract):
|
||||
require(isinstance(contract,dict),'contract_type')
|
||||
for key,value in FIXED.items():
|
||||
require(type(contract.get(key)) is type(value) and contract[key]==value,'contract_scope')
|
||||
for key in ('owner_review_ref','ssh_egress_existing_proof_ref','runtime_identity_ref','rollback_ref'):
|
||||
require(isinstance(contract.get(key),str) and contract[key].strip(),'missing_owner_proof')
|
||||
require(isinstance(contract.get('ssh_known_hosts_sha256'),str) and len(contract['ssh_known_hosts_sha256'])==64,'host_pin_required')
|
||||
require(datetime.now(timezone.utc)<datetime.fromisoformat(contract['expires_at']),'expired_contract')
|
||||
|
||||
|
||||
def secret_payload(private_key):
|
||||
return {'apiVersion':'v1','kind':'Secret','metadata':{'name':SECRET,'namespace':'argocd','labels':{'argocd.argoproj.io/secret-type':'repository','app.kubernetes.io/part-of':'railiance-gitops'},'annotations':{'railiance.io/credential-task':'RPF-WP-0044-T04'}},'type':'Opaque','stringData':{'type':'git','project':'railiance-tenants','url':URL,'sshPrivateKey':private_key}}
|
||||
|
||||
|
||||
def execute(contract,journal):
|
||||
validate_contract(contract)
|
||||
require(os.environ.get('API_TOKEN'),'provider_identity_missing')
|
||||
# Fixed native target over SSH; no workstation kube-context fallback.
|
||||
# Private destination material travels only in encrypted stdin, never argv.
|
||||
def kubectl(*args,body=None):return run(['ssh','-o','BatchMode=yes','-o','ConnectTimeout=10','railiance01',shlex.join(['kubectl','--request-timeout=15s',*args])],body)
|
||||
nodes=json.loads(kubectl('get','nodes','-o','json'))['items']
|
||||
require(len(nodes)==1 and any(a.get('address')=='92.205.62.239' for a in nodes[0]['status']['addresses']),'wrong_estate')
|
||||
known=json.loads(kubectl('-n','argocd','get','configmap','argocd-ssh-known-hosts-cm','-o','json'))['data']['ssh_known_hosts']
|
||||
require(hashlib.sha256(known.encode()).hexdigest()==contract['ssh_known_hosts_sha256'],'host_pin_changed')
|
||||
require('[forgejo.coulomb.social]:30022 ' in known,'host_pin_missing')
|
||||
project=json.loads(kubectl('-n','argocd','get','appproject','railiance-tenants','-o','json'))
|
||||
require(any(fnmatch.fnmatchcase(URL,p) for p in project.get('spec',{}).get('sourceRepos',[])),'project_source_refused')
|
||||
caps=run(['bao','token','capabilities','platform/data/operators/argocd/repositories/rapp-issue-core'])
|
||||
capability_set={x.strip() for x in caps.strip().split(',')}
|
||||
capability_mode='scoped' if {'create','update','read'}<=capability_set else 'attended-root' if capability_set=={'root'} and contract['execution_mode']=='attended-founder-bounded' else None
|
||||
require(capability_mode,'operatorpath_capability_missing')
|
||||
audit_devices=json.loads(run(['bao','audit','list','-format=json']))
|
||||
require(isinstance(audit_devices,dict) and 0<len(audit_devices)<=32,'enabled_audit_metadata_required')
|
||||
metadata=metadata_children()
|
||||
require('rapp-issue-core' not in metadata and 'rapp-issue-core/' not in metadata,'existing_custody_refused')
|
||||
# Metadata-presence read only: no Secret object or value is fetched.
|
||||
existing=kubectl('-n','argocd','get','secret',SECRET,'--ignore-not-found','-o','name')
|
||||
require(not existing.strip(),'existing_destination_refused')
|
||||
repo=api(os.environ['API_TOKEN'],'GET','/repos/'+REPO)
|
||||
require(repo.get('full_name')==REPO and repo.get('private') is True,'repository_identity')
|
||||
keys=api(os.environ['API_TOKEN'],'GET','/repos/'+REPO+'/keys?limit=100')
|
||||
require(isinstance(keys,list) and len(keys)<100,'deploy_key_list_bound')
|
||||
require(not any(k.get('title')==TITLE for k in keys),'existing_provider_key_refused')
|
||||
receipt={'schema':'argocd-issue-core-credential/v1','task':'RPF-WP-0044-T04','approval_ref':D2,'contract_sha256':hashlib.sha256(json.dumps(contract,sort_keys=True).encode()).hexdigest(),'state':'prepared','secret_values_observed_by_agent':False,'provider_key_id':None,'destination': 'argocd/'+SECRET,'wrap_ttl_seconds':60,'private_key_rotation_max_days':180,'execution_mode':'attended-founder-bounded','operatorpath_capability_mode':capability_mode,'audit_device_count':len(audit_devices),'audit_enabled_metadata':True}
|
||||
fd=os.open(journal,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600)
|
||||
def save(state):
|
||||
receipt['state']=state;receipt['updated_at']=datetime.now(timezone.utc).isoformat()
|
||||
os.lseek(fd,0,os.SEEK_SET);os.ftruncate(fd,0);os.write(fd,(json.dumps(receipt,sort_keys=True)+'\n').encode());os.fsync(fd)
|
||||
try:
|
||||
save('prepared')
|
||||
# Issuer uses private tmpfs; destination is the fixed native railiance01 API.
|
||||
with tempfile.TemporaryDirectory(prefix='argocd-issue-core-',dir='/dev/shm') as folder:
|
||||
private=Path(folder)/'key'
|
||||
run(['ssh-keygen','-q','-t','ed25519','-N','','-C',TITLE,'-f',str(private)])
|
||||
require(stat.S_IMODE(private.stat().st_mode)==0o600,'private_file_mode')
|
||||
public=Path(str(private)+'.pub').read_text().strip();key=private.read_text()
|
||||
receipt['public_key_sha256']=hashlib.sha256(public.encode()).hexdigest()
|
||||
save('provider_request_pending')
|
||||
created=api(os.environ['API_TOKEN'],'POST','/repos/'+REPO+'/keys',{'title':TITLE,'key':public,'read_only':True},expected=(201,))
|
||||
require(type(created.get('id')) is int and created.get('read_only') is True and created.get('key','').split()[:2]==public.split()[:2],'provider_key_unverified')
|
||||
receipt['provider_key_id']=created['id'];save('provider_created')
|
||||
saved=run(['bao','kv','put','-format=json','-cas=0',BAO,'SSH_PRIVATE_KEY=-','SSH_PUBLIC_KEY='+public,'PROVIDER_KEY_ID='+str(created['id']),'REPOSITORY='+REPO],key)
|
||||
version=json.loads(saved).get('data',{}).get('version');require(type(version) is int,'custody_version_unverified')
|
||||
receipt['custody_version']=version;receipt['custody_write_request_id']=json.loads(saved).get('request_id');save('custody_stored')
|
||||
wrap=json.loads(run(['bao','kv','get','-wrap-ttl=60s','-format=json',BAO]))['wrap_info']
|
||||
require(wrap.get('ttl')==60 and isinstance(wrap.get('token'),str) and wrap.get('creation_path')=='platform/data/operators/argocd/repositories/rapp-issue-core','wrap_unverified')
|
||||
receipt['wrap_creation_path']=wrap['creation_path']
|
||||
receipt['wrap_accessor']=wrap.get('accessor');save('unwrap_request_pending')
|
||||
# Token travels only in this fixed child environment, never argv/stdout.
|
||||
wrap_env={**os.environ,'BAO_TOKEN':wrap['token'],'VAULT_TOKEN':wrap['token']}
|
||||
unwrapped=json.loads(run(['bao','unwrap','-format=json'],env=wrap_env))
|
||||
receipt['unwrap_request_id']=unwrapped.get('request_id');delivered=unwrapped['data']['data'];require(delivered['SSH_PRIVATE_KEY']==key and str(delivered['PROVIDER_KEY_ID'])==str(created['id']),'unwrap_content_unverified');save('unwrap_consumed')
|
||||
require(replay_denied(wrap_env),'consumed_wrap_replay_not_proven')
|
||||
receipt['consumed_wrap_replay_denied_400']=True;save('consumed_replay_denied')
|
||||
manifest=json.dumps(secret_payload(delivered['SSH_PRIVATE_KEY']))
|
||||
kubectl('create','--dry-run=server','-f','-','-o','name',body=manifest)
|
||||
save('destination_request_pending')
|
||||
kubectl('create','--field-manager=argocd-issue-core-credential','-f','-','-o','name',body=manifest)
|
||||
# No client-side apply annotation; only one exact Secret is created.
|
||||
require(kubectl('-n','argocd','get','secret',SECRET,'-o','name').strip()=='secret/'+SECRET,'destination_unverified')
|
||||
save('delivered_not_adopted')
|
||||
except BaseException:
|
||||
save('uncertain_do_not_replay');raise
|
||||
finally:os.close(fd)
|
||||
|
||||
|
||||
def main():
|
||||
parser=argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--inner',action='store_true');parser.add_argument('--contract',required=True,type=Path);parser.add_argument('--contract-sha256',required=True);parser.add_argument('--journal',required=True,type=Path)
|
||||
args=parser.parse_args()
|
||||
# Suppress the inherited fetch stderr and all child output, including tracebacks.
|
||||
sink=os.open(os.devnull,os.O_WRONLY);os.dup2(sink,1);os.dup2(sink,2);os.close(sink)
|
||||
try:
|
||||
raw=args.contract.read_bytes();require(hashlib.sha256(raw).hexdigest()==args.contract_sha256,'contract_changed')
|
||||
validate_contract(json.loads(raw));require(not args.journal.exists(),'journal_exists_do_not_replay')
|
||||
if not args.inner:
|
||||
source=Path(__file__).resolve()
|
||||
result=subprocess.run(['warden','access','forgejo-admin-api-token','--field','API_TOKEN','--exec','--',sys.executable,str(source),'--inner','--contract',str(args.contract),'--contract-sha256',args.contract_sha256,'--journal',str(args.journal)],capture_output=True,timeout=900)
|
||||
return 0 if result.returncode==0 else 1
|
||||
execute(json.loads(raw),args.journal);return 0
|
||||
except BaseException as error:
|
||||
# Fixed diagnostic only; never overwrite a prior/uncertain execution journal.
|
||||
failure=str(error) if isinstance(error,Refused) else 'internal_outcome_unverified'
|
||||
try:
|
||||
fd=os.open(args.journal,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600)
|
||||
os.write(fd,(json.dumps({'schema':'argocd-issue-core-credential/v1','state':'preflight_refused','failure':failure,'secret_values_observed_by_agent':False})+'\n').encode());os.fsync(fd);os.close(fd)
|
||||
except Exception:pass
|
||||
return 1
|
||||
|
||||
|
||||
if __name__=='__main__':raise SystemExit(main())
|
||||
245
scripts/workload_adoption_proof.py
Normal file
245
scripts/workload_adoption_proof.py
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Read-only manual workload adoption proof; never enables automation or runs hooks."""
|
||||
from datetime import datetime, timezone
|
||||
import argparse
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
from urllib.request import urlopen
|
||||
|
||||
from argocd_adoption_proof import checked_run, identity, timestamp, tracking_settings
|
||||
|
||||
PIN = 'a3a8a27a8cda32ae3c7b55353ee16a131c50a0a0'
|
||||
CONTRACT = {
|
||||
'estate_ip': '92.205.62.239', 'application': 'target-revenue',
|
||||
'project': 'railiance-tenants', 'destination': {'server': 'https://kubernetes.default.svc', 'namespace': 'target-revenue'},
|
||||
'repo_url': 'https://forgejo.coulomb.social/coulomb/target-revenue.git', 'source_path': 'k8s/railiance',
|
||||
'revision': PIN, 'tracking_method': 'annotation', 'controller_version': 'v3.5.3',
|
||||
'minimum_proving_seconds': 86400, 'maximum_observation_age_seconds': 300,
|
||||
'health_url': 'https://revenue.coulomb.social/healthz',
|
||||
'resources': [{'group': group, 'kind': kind, 'namespace': 'target-revenue', 'name': name}
|
||||
for group, kind, name in [('', 'Service', 'target-revenue'), ('apps', 'Deployment', 'target-revenue'),
|
||||
('external-secrets.io', 'ExternalSecret', 'target-revenue-runtime'),
|
||||
('networking.k8s.io', 'Ingress', 'target-revenue'), ('postgresql.cnpg.io', 'Cluster', 'target-revenue-pg')]],
|
||||
'hooks': ['target-revenue-bootstrap-binky', 'target-revenue-migrate'],
|
||||
'ready_checks': [{'kind': kind, 'namespace': 'target-revenue', 'name': name, 'condition': condition}
|
||||
for kind, name, condition in [('Deployment', 'target-revenue', 'Available'),
|
||||
('ExternalSecret', 'target-revenue-runtime', 'Ready'), ('Cluster', 'target-revenue-pg', 'Ready')]],
|
||||
}
|
||||
|
||||
|
||||
def validate_contract(contract):
|
||||
if (not re.fullmatch(r'[a-f0-9]{40}', contract['revision']) or contract['minimum_proving_seconds'] < 86400
|
||||
or not 0 < contract['maximum_observation_age_seconds'] <= 300):
|
||||
raise ValueError('invalid proof bounds')
|
||||
if any(r['kind'] not in {'Service', 'Deployment', 'ExternalSecret', 'Ingress', 'Cluster'} for r in contract['resources']):
|
||||
raise ValueError('resource kinds not allowlisted')
|
||||
if len({identity(r) for r in contract['resources']}) != len(contract['resources']):
|
||||
raise ValueError('duplicate resource identities')
|
||||
for r in [*contract['resources'], *contract['ready_checks']]:
|
||||
if r['kind'] not in {'Service', 'Deployment', 'ExternalSecret', 'Ingress', 'Cluster'}:
|
||||
raise ValueError('readiness kind not allowlisted')
|
||||
if any(not re.fullmatch(r'[a-z0-9][a-z0-9.-]*', r[k]) for k in ('name', 'namespace')):
|
||||
raise ValueError('invalid resource identity')
|
||||
|
||||
|
||||
def normalize(items, contract, settings):
|
||||
validate_contract(contract)
|
||||
if (settings['method'] != 'annotation' or settings['controller_version'] != contract['controller_version']
|
||||
or settings['installation_id_absent'] is not True):
|
||||
raise ValueError('tracking semantics unverified')
|
||||
resources, hooks = [], []
|
||||
for raw in items:
|
||||
obj = copy.deepcopy(raw)
|
||||
meta = obj['metadata']
|
||||
annotation = meta.get('annotations', {})
|
||||
if 'argocd.argoproj.io/hook' in annotation:
|
||||
if obj['kind'] != 'Job' or annotation['argocd.argoproj.io/hook'] != 'Sync':
|
||||
raise ValueError('unsupported hook')
|
||||
hooks.append(meta['name'])
|
||||
continue
|
||||
group = obj['apiVersion'].split('/')[0] if '/' in obj['apiVersion'] else ''
|
||||
ident = (group, obj['kind'], meta.get('namespace', ''), meta['name'])
|
||||
if ident not in {identity(r) for r in contract['resources']}:
|
||||
raise ValueError('render resource not admitted')
|
||||
tracking = f"{contract['application']}:{group}/{obj['kind']}:{meta['namespace']}/{meta['name']}"
|
||||
annotations = meta.setdefault('annotations', {})
|
||||
if annotations.get('argocd.argoproj.io/tracking-id', tracking) != tracking:
|
||||
raise ValueError('conflicting source tracking annotation')
|
||||
annotations['argocd.argoproj.io/tracking-id'] = tracking
|
||||
resources.append(obj)
|
||||
if (len(resources) != len(contract['resources']) or len({(r['apiVersion'], r['kind'], r['metadata']['name']) for r in resources}) != len(resources)
|
||||
or sorted(hooks) != sorted(contract['hooks'])):
|
||||
raise ValueError('render resource or hook set differs')
|
||||
return resources
|
||||
|
||||
|
||||
def decode_objects(raw):
|
||||
text = raw.decode()
|
||||
decoder = json.JSONDecoder()
|
||||
items = []
|
||||
while text.strip():
|
||||
value, end = decoder.raw_decode(text.lstrip())
|
||||
items.extend(value["items"] if value.get("kind") == "List" else [value])
|
||||
text = text.lstrip()[end:]
|
||||
return items
|
||||
|
||||
|
||||
def collect(rendered, contract):
|
||||
validate_contract(contract)
|
||||
def get(args):
|
||||
return json.loads(checked_run(['kubectl', *args, '-o', 'json']))
|
||||
nodes = get(['get', 'nodes'])
|
||||
if contract['estate_ip'] not in [a['address'] for n in nodes['items'] for a in n['status']['addresses']]:
|
||||
raise ValueError('estate identity unverified')
|
||||
settings = tracking_settings(contract)
|
||||
parsed = decode_objects(checked_run(['kubectl', 'create', '--dry-run=client', '--validate=false', '-f', '-', '-o', 'json'], input=rendered))
|
||||
desired = normalize(parsed, contract, settings)
|
||||
payload = json.dumps({'apiVersion': 'v1', 'kind': 'List', 'items': desired}, sort_keys=True).encode()
|
||||
diff = subprocess.run(['kubectl', 'diff', '-f', '-'], input=payload, capture_output=True, timeout=120)
|
||||
if diff.returncode not in (0, 1):
|
||||
raise ValueError('native client diff failed; protected output suppressed')
|
||||
obj = get(['-n', 'argocd', 'get', 'application', contract['application']])
|
||||
spec, status = obj['spec'], obj.get('status', {})
|
||||
op = status.get('operationState', {})
|
||||
sync = op.get('operation', {}).get('sync', {})
|
||||
checks = []
|
||||
for check in contract['ready_checks']:
|
||||
r = get(['-n', check['namespace'], 'get', check['kind'], check['name']])
|
||||
ready = [{k: c.get(k) for k in ('status', 'lastTransitionTime')} for c in r.get('status', {}).get('conditions', []) if c.get('type') == check['condition']]
|
||||
group = r['apiVersion'].split('/')[0] if '/' in r['apiVersion'] else ''
|
||||
tracking = f"{contract['application']}:{group}/{r['kind']}:{check['namespace']}/{check['name']}"
|
||||
checks.append({**check, 'uid': r['metadata']['uid'], 'ready': ready,
|
||||
'tracking_matches': r['metadata'].get('annotations', {}).get('argocd.argoproj.io/tracking-id') == tracking,
|
||||
'observed_generation': r.get('status', {}).get('observedGeneration'), 'generation': r['metadata'].get('generation'),
|
||||
'replicas': r.get('status', {}).get('replicas'), 'available_replicas': r.get('status', {}).get('availableReplicas'),
|
||||
'instances': r.get('status', {}).get('instances'), 'ready_instances': r.get('status', {}).get('readyInstances')})
|
||||
jobs = get(['-n', contract['destination']['namespace'], 'get', 'jobs'])
|
||||
live_hooks = sorted(r['metadata']['name'] for r in jobs['items'] if r['metadata']['name'] in contract['hooks'])
|
||||
return {'captured_at': datetime.now(timezone.utc).isoformat(), 'estate_ip': contract['estate_ip'], 'tracking_settings': settings,
|
||||
'application': {'name': obj['metadata']['name'], 'uid': obj['metadata']['uid'], 'project': spec.get('project'),
|
||||
'source': {k: spec.get('source', {}).get(k) for k in ('repoURL', 'path', 'targetRevision')}, 'destination': spec.get('destination'),
|
||||
'automated_present': 'automated' in spec.get('syncPolicy', {}), 'pending_operation': bool(obj.get('operation')),
|
||||
'sync': status.get('sync', {}).get('status'), 'health': status.get('health', {}).get('status'), 'revision': status.get('sync', {}).get('revision'),
|
||||
'condition_types': [c.get('type') for c in status.get('conditions', [])],
|
||||
'operation': {k: op.get(k) for k in ('phase', 'startedAt', 'finishedAt')},
|
||||
'sync_prune': sync.get('prune', False), 'sync_strategy_apply': 'apply' in sync.get('syncStrategy', {}),
|
||||
'sync_strategy_force': sync.get('syncStrategy', {}).get('apply', {}).get('force', False), 'operation_revision': sync.get('revision'),
|
||||
'resources': [{k: r.get(k) for k in ('group', 'kind', 'namespace', 'name', 'status')} for r in status.get('resources', [])]},
|
||||
'ready_checks': checks, 'live_hooks': live_hooks,
|
||||
'pinned_render_diff': {'revision': contract['revision'], 'path': contract['source_path'], 'client_side_exit_code': diff.returncode,
|
||||
'render_sha256': hashlib.sha256(rendered).hexdigest(), 'normalized_render_sha256': hashlib.sha256(payload).hexdigest(),
|
||||
'render_resource_count': len(desired), 'skipped_hooks': sorted(contract['hooks']), 'output_suppressed': True}}
|
||||
|
||||
|
||||
def verify(observation, contract, now=None):
|
||||
validate_contract(contract)
|
||||
now = now or datetime.now(timezone.utc)
|
||||
captured = timestamp(observation['captured_at'])
|
||||
if not 0 <= (now - captured).total_seconds() <= contract['maximum_observation_age_seconds']:
|
||||
raise ValueError('observation stale or future')
|
||||
if not -5 <= (captured - timestamp(observation['metadata_captured_at'])).total_seconds() <= contract['maximum_observation_age_seconds']:
|
||||
raise ValueError('metadata observation stale or clock skew exceeds bound')
|
||||
settings = observation['tracking_settings']
|
||||
if (observation['estate_ip'] != contract['estate_ip'] or settings.get('method') != contract['tracking_method']
|
||||
or settings.get('controller_version') != contract['controller_version'] or settings.get('installation_id_absent') is not True):
|
||||
raise ValueError('estate or tracking semantics unverified')
|
||||
app = observation['application']
|
||||
if (app['name'] != contract['application'] or not app['uid'] or app['project'] != contract['project'] or app['destination'] != contract['destination']
|
||||
or app['source'] != {'repoURL': contract['repo_url'], 'path': contract['source_path'], 'targetRevision': contract['revision']}
|
||||
or app['revision'] != contract['revision']):
|
||||
raise ValueError('source or application identity mismatch')
|
||||
if (app['automated_present'] is not False or app['pending_operation'] is not False or app['sync'] != 'Synced' or app['health'] != 'Healthy'
|
||||
or any(c != 'OrphanedResourceWarning' for c in app['condition_types'])):
|
||||
raise ValueError('manual idle healthy adoption unverified')
|
||||
op = app['operation']
|
||||
if (op['phase'] != 'Succeeded' or app['sync_prune'] is not False or app['sync_strategy_apply'] is not True or app['sync_strategy_force'] is not False or app['operation_revision'] != contract['revision']):
|
||||
raise ValueError('pinned manual apply strategy unverified')
|
||||
start, finish = timestamp(op['startedAt']), timestamp(op['finishedAt'])
|
||||
proof_age = (captured - finish).total_seconds()
|
||||
if finish < start or proof_age < contract['minimum_proving_seconds']:
|
||||
raise ValueError('completed 24h proving period unverified')
|
||||
expected = {identity(r) for r in contract['resources']}
|
||||
if (len(app['resources']) != len(expected) or {identity(r) for r in app['resources']} != expected or any(r['status'] != 'Synced' for r in app['resources'])):
|
||||
raise ValueError('exact synced resource set unverified')
|
||||
checks = observation['ready_checks']
|
||||
expected_checks = {(c['kind'], c['namespace'], c['name'], c['condition']) for c in contract['ready_checks']}
|
||||
if len(checks) != len(expected_checks) or {(c['kind'], c['namespace'], c['name'], c['condition']) for c in checks} != expected_checks:
|
||||
raise ValueError('readiness checks incomplete')
|
||||
for c in checks:
|
||||
if not c['uid'] or c['tracking_matches'] is not True or len(c['ready']) != 1 or c['ready'][0]['status'] != 'True':
|
||||
raise ValueError('tracked resource readiness unverified')
|
||||
if timestamp(c['ready'][0]['lastTransitionTime']) > start:
|
||||
raise ValueError('readiness changed during proving period')
|
||||
if c['kind'] == 'Deployment' and (c['observed_generation'] != c['generation'] or not c['replicas'] or c['available_replicas'] != c['replicas']):
|
||||
raise ValueError('deployment availability incomplete')
|
||||
if c['kind'] == 'Cluster' and (not c['instances'] or c['ready_instances'] != c['instances']):
|
||||
raise ValueError('CNPG readiness incomplete')
|
||||
diff = observation['pinned_render_diff']
|
||||
if (diff.get('revision') != contract['revision'] or diff.get('path') != contract['source_path'] or type(diff.get('client_side_exit_code')) is not int
|
||||
or diff['client_side_exit_code'] != 0 or diff.get('render_resource_count') != len(expected)
|
||||
or diff.get('skipped_hooks') != sorted(contract['hooks']) or diff.get('output_suppressed') is not True
|
||||
or any(not re.fullmatch(r'[a-f0-9]{64}', diff.get(k, '')) for k in ('render_sha256', 'normalized_render_sha256'))):
|
||||
raise ValueError('pinned normalized client zero-diff unverified')
|
||||
if observation.get('live_hooks') != [] or observation.get('public_health') != {'url': contract['health_url'], 'status': 200}:
|
||||
raise ValueError('hook absence or public health unverified')
|
||||
return {'schema_version': 'argocd-workload-adoption-proof/v1', 'status': 'verified', 'application': contract['application'],
|
||||
'revision': contract['revision'], 'captured_at': observation['captured_at'], 'verified_at': now.isoformat(),
|
||||
'proving_seconds': proof_age, 'resource_count': len(expected), 'ready_count': len(checks),
|
||||
'manual_apply_prune_off': True, 'hooks_skipped': sorted(contract['hooks']), 'hooks_absent': True,
|
||||
'child_automation_enabled': False, 'public_health_status': 200, 'native_mutation': False,
|
||||
'verifier_sha256': hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
|
||||
'tracking_helper_sha256': hashlib.sha256(Path(__file__).with_name('argocd_adoption_proof.py').read_bytes()).hexdigest(),
|
||||
'contract_sha256': hashlib.sha256(json.dumps(contract, sort_keys=True).encode()).hexdigest(),
|
||||
'observation_sha256': hashlib.sha256(json.dumps(observation, sort_keys=True).encode()).hexdigest()}
|
||||
|
||||
|
||||
def remote_program(common, rendered):
|
||||
import base64
|
||||
return ('import sys,types,base64,json\n' + 'm=types.ModuleType("argocd_adoption_proof");sys.modules[m.__name__]=m\n'
|
||||
+ 'exec(compile(base64.b64decode(' + repr(base64.b64encode(common).decode()) + '),"argocd_adoption_proof.py","exec"),m.__dict__)\n'
|
||||
+ 'n={"__name__":"workload_proof"};exec(compile(base64.b64decode(' + repr(base64.b64encode(Path(__file__).read_bytes()).decode())
|
||||
+ '),"workload_adoption_proof.py","exec"),n)\n'
|
||||
+ 'try:\n print(json.dumps(n["collect"](base64.b64decode(' + repr(base64.b64encode(rendered).decode()) + '),n["CONTRACT"]),sort_keys=True))\n'
|
||||
+ 'except Exception:\n raise SystemExit("adoption observation incomplete; protected output suppressed")\n').encode()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--live', action='store_true')
|
||||
parser.add_argument('--source-root', type=Path)
|
||||
parser.add_argument('--observation', type=Path, required=True)
|
||||
parser.add_argument('--receipt', type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
if args.live:
|
||||
if not args.source_root:
|
||||
raise ValueError('source root required')
|
||||
import tempfile
|
||||
with tempfile.TemporaryDirectory(prefix='workload-adoption-pin-') as temp:
|
||||
archive = checked_run(['git', '-C', str(args.source_root), 'archive', CONTRACT['revision'], CONTRACT['source_path']])
|
||||
checked_run(['tar', '-x', '-C', temp], input=archive)
|
||||
rendered = checked_run(['kubectl', 'kustomize', str(Path(temp) / CONTRACT['source_path'])])
|
||||
common = Path(__file__).with_name('argocd_adoption_proof.py').read_bytes()
|
||||
observation = json.loads(checked_run(['ssh', 'railiance01', 'python3', '-'], input=remote_program(common, rendered), timeout=180))
|
||||
with urlopen(CONTRACT['health_url'], timeout=30) as response:
|
||||
observation['public_health'] = {'url': response.geturl(), 'status': response.status}
|
||||
observation['metadata_captured_at'] = observation['captured_at']
|
||||
observation['captured_at'] = datetime.now(timezone.utc).isoformat()
|
||||
args.observation.write_text(json.dumps(observation, indent=2) + '\n')
|
||||
else:
|
||||
observation = json.loads(args.observation.read_text())
|
||||
receipt = verify(observation, CONTRACT)
|
||||
except Exception:
|
||||
receipt = {'schema_version': 'argocd-workload-adoption-proof/v1', 'status': 'unverified',
|
||||
'error': 'adoption proof incomplete; protected output suppressed', 'native_mutation': False}
|
||||
args.receipt.write_text(json.dumps(receipt, indent=2) + '\n')
|
||||
print(json.dumps(receipt, sort_keys=True))
|
||||
return 0 if receipt['status'] == 'verified' else 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
22
tests/probe_bao_cli_synthetic.py
Normal file
22
tests/probe_bao_cli_synthetic.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import hashlib,json,os,subprocess,threading
|
||||
from http.server import BaseHTTPRequestHandler,HTTPServer
|
||||
captured=[]
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def log_message(self,*args):pass
|
||||
def do_GET(self):
|
||||
if '/metadata/' in self.path:
|
||||
self.send_response(404);self.end_headers();self.wfile.write(b'{"errors":[]}');return
|
||||
self.send_response(200);self.end_headers();self.wfile.write(b'{"data":{"path":"platform/","type":"kv","options":{"version":"2"}}}')
|
||||
def do_POST(self):self.do_PUT()
|
||||
def do_PUT(self):
|
||||
body=self.rfile.read(int(self.headers.get('Content-Length','0')));captured.append((self.command,self.path,json.loads(body or b'{}')))
|
||||
if self.path=='/v1/sys/wrapping/unwrap':self.send_response(400);self.end_headers();self.wfile.write(b'{"errors":["wrapping token is not valid or does not exist"]}')
|
||||
else:self.send_response(200);self.end_headers();self.wfile.write(b'{"data":{"version":1}}')
|
||||
def do_LIST(self):self.send_response(404);self.end_headers();self.wfile.write(b'{"errors":[]}')
|
||||
server=HTTPServer(('127.0.0.1',0),Handler);threading.Thread(target=server.serve_forever,daemon=True).start()
|
||||
env={**os.environ,'BAO_ADDR':'http://127.0.0.1:'+str(server.server_port),'VAULT_ADDR':'http://127.0.0.1:'+str(server.server_port),'BAO_TOKEN':'SYNTHETIC_NON_SECRET_FIXTURE','VAULT_TOKEN':'SYNTHETIC_NON_SECRET_FIXTURE'}
|
||||
put=subprocess.run(['bao','kv','put','-format=json','-cas=0','platform/operators/argocd/repositories/rapp-issue-core','SSH_PRIVATE_KEY=-','REPOSITORY=coulomb/rapp-issue-core'],input='SYNTHETIC_KEY_FIXTURE\n',text=True,env=env,capture_output=True)
|
||||
listing=subprocess.run(['bao','list','-format=json','platform/metadata/operators/argocd/repositories/'],env=env,capture_output=True,text=True)
|
||||
unwrap=subprocess.run(['bao','unwrap','-format=json'],env=env,capture_output=True,text=True)
|
||||
server.shutdown()
|
||||
print(json.dumps({'synthetic_only':True,'installed_cli_kv_put_exit':put.returncode,'stdin_field_exact':any(item[1]=='/v1/platform/data/operators/argocd/repositories/rapp-issue-core' and item[2].get('data',{}).get('SSH_PRIVATE_KEY')=='SYNTHETIC_KEY_FIXTURE\n' and item[2].get('options',{}).get('cas')==0 for item in captured),'list_404_exit':listing.returncode,'list_404_safe_error':(listing.stdout+listing.stderr).strip(),'unwrap_400_exit':unwrap.returncode,'unwrap_400_safe_error':(unwrap.stdout+unwrap.stderr).strip()},indent=2))
|
||||
127
tests/test_activity_promotion_readiness.py
Normal file
127
tests/test_activity_promotion_readiness.py
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
import base64
|
||||
from datetime import datetime, timezone, timedelta
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
from unittest import mock
|
||||
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
||||
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
||||
|
||||
SPEC=importlib.util.spec_from_file_location('readiness',Path(__file__).parents[1]/'scripts/activity_promotion_readiness.py')
|
||||
p=importlib.util.module_from_spec(SPEC);SPEC.loader.exec_module(p)
|
||||
NOW=datetime(2026,10,11,20,tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def observation():
|
||||
return {'captured_at':NOW.isoformat(),'estate_ip':'92.205.62.239','cluster_uid':'cluster-uid','application_uid':'app-uid',
|
||||
'source':{'repoURL':'https://forgejo.coulomb.social/coulomb/activity-core.git','path':'k8s/gitops','targetRevision':p.PIN},
|
||||
'project':'activity-core','revision':p.PIN,'destination':{'server':'https://kubernetes.default.svc','namespace':'activity-core'},
|
||||
'automated_present':False,'pending_operation':False,
|
||||
'sync_options':['CreateNamespace=false','ApplyOutOfSyncOnly=true','PruneLast=true','FailOnSharedResource=true'],
|
||||
'sync':'Synced','health':'Healthy','condition_types':[],'project_scope_matches':True,
|
||||
'operation':{'phase':'Succeeded','finishedAt':'2026-10-04T14:13:25Z'},
|
||||
'resources':[dict(zip(('group','kind','namespace','name'),r),status='Synced') for r in sorted(p.RESOURCES)],
|
||||
'deployments':[{'name':name,'generation_observed':True,'replicas':1,'ready':1,'updated':1,'available':1}
|
||||
for name in ('actcore-api','actcore-worker','actcore-event-router')]}
|
||||
|
||||
|
||||
def payload():
|
||||
return {'schema_version':'activity-core-release-acceptance/v1','owner_task':p.OWNER_TASK,'issuer':'owner-acceptance-principal',
|
||||
'authority':p.AUTHORITY,'application':'activity-core','cluster_uid':'cluster-uid','adoption_revision':p.PIN,
|
||||
'targets':dict(p.TARGETS),'release_principal':'activity-core-release-broker','broker_revision':'a'*40,
|
||||
'checks':{c:True for c in p.CHECKS},'issued_at':NOW.isoformat(),'expires_at':(NOW+timedelta(minutes=4)).isoformat(),
|
||||
'continuous_health':{'revision':p.PIN,'continuous':True,'max_gap_seconds':90,'observed_at':NOW.isoformat(),
|
||||
'healthy_since':(NOW-timedelta(days=1)).isoformat()}}
|
||||
|
||||
|
||||
class GateTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.private=Ed25519PrivateKey.generate()
|
||||
self.public=self.private.public_key().public_bytes(Encoding.Raw,PublicFormat.Raw)
|
||||
|
||||
def envelope(self,data=None):
|
||||
data=data or payload()
|
||||
return {'payload':data,'signature':base64.b64encode(self.private.sign(p.canonical(data))).decode()}
|
||||
|
||||
def evaluate(self,envelope=None,obs=None,key=None):
|
||||
return p.readiness(obs or observation(),envelope=envelope or self.envelope(),trusted_key=key or self.public,
|
||||
trusted_principal='owner-acceptance-principal',now=NOW)
|
||||
|
||||
def test_complete_signed_fixture_passes_without_activation(self):
|
||||
result=self.evaluate()
|
||||
self.assertEqual(result['status'],'ready')
|
||||
self.assertFalse(result['native_mutation'])
|
||||
self.assertFalse(result['automatic_sync_candidate'])
|
||||
|
||||
def test_snapshot_and_elapsed_time_cannot_substitute_for_owner_proof(self):
|
||||
result=p.readiness(observation(),now=NOW)
|
||||
self.assertEqual(result['status'],'waiting')
|
||||
self.assertEqual(result['depends_on'],['ACTIVITY-WP-0041-T03'])
|
||||
self.assertFalse(result['needs_human'])
|
||||
self.assertTrue(result['adoption_snapshot_is_not_continuous_observation'])
|
||||
|
||||
def test_every_acceptance_check_required(self):
|
||||
for check in p.CHECKS:
|
||||
for missing in (False,None,'true'):
|
||||
data=payload();data['checks'][check]=missing
|
||||
with self.subTest(check=check,value=missing):self.assertEqual(self.evaluate(self.envelope(data))['status'],'unverified')
|
||||
|
||||
def test_unknown_key_forged_tampered_or_self_introduced_trust_refused(self):
|
||||
envelope=self.envelope();envelope['payload']['release_principal']='attacker'
|
||||
self.assertEqual(self.evaluate(envelope)['status'],'unverified')
|
||||
unknown=Ed25519PrivateKey.generate().public_key().public_bytes(Encoding.Raw,PublicFormat.Raw)
|
||||
self.assertEqual(self.evaluate(key=unknown)['status'],'unverified')
|
||||
envelope=self.envelope();envelope['public_key']=self.public.hex()
|
||||
self.assertEqual(self.evaluate(envelope)['status'],'unverified')
|
||||
envelope=self.envelope();envelope['signature']='invalid-base64!'
|
||||
self.assertEqual(self.evaluate(envelope)['status'],'unverified')
|
||||
|
||||
def test_wrong_scope_identity_cluster_commit_or_expired_owner_proof_refused(self):
|
||||
for key,value in [('authority','admin'),('application','other'),('cluster_uid','other'),
|
||||
('adoption_revision','b'*40),('owner_task','other'),('issuer','producer'),
|
||||
('broker_revision','main'),('release_principal',''),
|
||||
('issued_at',(NOW-timedelta(minutes=6)).isoformat()),
|
||||
('issued_at',(NOW+timedelta(seconds=1)).isoformat()),
|
||||
('expires_at',NOW.isoformat())]:
|
||||
data=payload();data[key]=value
|
||||
with self.subTest(key=key):self.assertEqual(self.evaluate(self.envelope(data))['status'],'unverified')
|
||||
for key,value in [('prune',True),('root_sync_selects_only','*'),('child_path','other')]:
|
||||
data=payload();data['targets'][key]=value
|
||||
with self.subTest(target=key):self.assertEqual(self.evaluate(self.envelope(data))['status'],'unverified')
|
||||
|
||||
def test_continuous_sampling_requires_owner_signed_fresh_bounded_interval(self):
|
||||
for key,value in [('continuous',False),('max_gap_seconds',91),('max_gap_seconds',True),
|
||||
('revision','b'*40),('healthy_since',(NOW-timedelta(hours=23)).isoformat()),
|
||||
('observed_at',(NOW-timedelta(seconds=91)).isoformat()),
|
||||
('observed_at',(NOW+timedelta(seconds=1)).isoformat())]:
|
||||
data=payload();data['continuous_health'][key]=value
|
||||
with self.subTest(key=key):self.assertEqual(self.evaluate(self.envelope(data))['status'],'unverified')
|
||||
|
||||
def test_exact_live_adoption_scope_and_freshness_refusals(self):
|
||||
for key,value in [('estate_ip','other'),('project_scope_matches',False),('project','other'),
|
||||
('automated_present',True),('pending_operation',True),('revision','b'*40),
|
||||
('health','Degraded'),('sync','OutOfSync'),('condition_types',['ComparisonError']),
|
||||
('captured_at',(NOW-timedelta(minutes=6)).isoformat()),
|
||||
('captured_at',(NOW+timedelta(seconds=1)).isoformat()),
|
||||
('resources',[]),('deployments',[])]:
|
||||
data=observation();data[key]=value
|
||||
with self.subTest(key=key):self.assertEqual(self.evaluate(obs=data)['status'],'unverified')
|
||||
data=observation();data['resources'].append(data['resources'][0])
|
||||
self.assertEqual(self.evaluate(obs=data)['status'],'unverified')
|
||||
for key,value in [('ready',0),('generation_observed',False),('updated',0),('replicas',0)]:
|
||||
data=observation();data['deployments'][0][key]=value
|
||||
with self.subTest(deployment=key):self.assertEqual(self.evaluate(obs=data)['status'],'unverified')
|
||||
|
||||
def test_no_signature_verification_attempt_without_trusted_operator_config(self):
|
||||
self.assertEqual(p.readiness(observation(),envelope=self.envelope(),now=NOW)['status'],'waiting')
|
||||
|
||||
def test_protected_subprocess_errors_and_timeouts_never_leak_canary(self):
|
||||
import subprocess
|
||||
from types import SimpleNamespace
|
||||
canary='SECRET-CANARY-NOT-OUTPUT'
|
||||
with mock.patch.object(p.subprocess,'run',return_value=SimpleNamespace(returncode=1,stdout=canary,stderr=canary)):
|
||||
with self.assertRaises(ValueError) as exc:p.run(['kubectl','get','nodes'])
|
||||
self.assertNotIn(canary,str(exc.exception))
|
||||
with mock.patch.object(p.subprocess,'run',side_effect=subprocess.TimeoutExpired('cmd',1,output=canary,stderr=canary)):
|
||||
with self.assertRaises(ValueError) as exc:p.run(['kubectl','get','nodes'])
|
||||
self.assertNotIn(canary,str(exc.exception))
|
||||
92
tests/test_issue_core_credential_executor.py
Normal file
92
tests/test_issue_core_credential_executor.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
spec=importlib.util.spec_from_file_location('issuer',Path(__file__).resolve().parents[1]/'scripts/provision_argocd_issue_core_repository.py')
|
||||
issuer=importlib.util.module_from_spec(spec);spec.loader.exec_module(issuer)
|
||||
|
||||
class IssuerTests(unittest.TestCase):
|
||||
def contract(self):return {**issuer.FIXED,'owner_review_ref':'fixed-review','ssh_egress_existing_proof_ref':'fixed-egress','runtime_identity_ref':'attended-platform','rollback_ref':'fixed-rollback','ssh_known_hosts_sha256':'a'*64,'expires_at':'2099-01-01T00:00:00+00:00'}
|
||||
def test_existing_d2_covers_exact_scope_but_broadening_refused(self):
|
||||
issuer.validate_contract(self.contract())
|
||||
for key,value in [('repository','coulomb/*'),('read_only',False),('scope_approval_ref','self-approved'),('destination','argocd/broad-creds'),('new_network_scope',True)]:
|
||||
data=self.contract();data[key]=value
|
||||
with self.assertRaises(issuer.Refused):issuer.validate_contract(data)
|
||||
def test_wrong_estate_prevents_credentials_and_grants(self):
|
||||
calls=[]
|
||||
def runner(args,*a,**kw):
|
||||
calls.append(args);return json.dumps({'items':[{'status':{'addresses':[{'address':'192.0.2.1'}]}}]})
|
||||
with patch.dict(os.environ,{'API_TOKEN':'SYNTHETIC_TEST_ONLY'}),patch.object(issuer,'run',runner),patch.object(issuer,'api') as api:
|
||||
with self.assertRaises(issuer.Refused):issuer.execute(self.contract(),Path('/tmp/unused-fixture-journal'))
|
||||
api.assert_not_called()
|
||||
self.assertEqual(len(calls),1)
|
||||
def test_provider_cannot_redirect_or_use_other_repo(self):
|
||||
with self.assertRaises(issuer.Refused):issuer.api('synthetic','GET','/repos/coulomb/other/keys')
|
||||
with self.assertRaises(issuer.Refused):issuer.NoRedirect().redirect_request(None,None,None,None,None,None)
|
||||
def test_secret_has_exact_scope_no_apply_annotation(self):
|
||||
body=issuer.secret_payload('SYNTHETIC_PRIVATE_CANARY')
|
||||
self.assertEqual(body['metadata']['namespace'],'argocd');self.assertEqual(body['metadata']['name'],issuer.SECRET)
|
||||
self.assertNotIn('kubectl.kubernetes.io/last-applied-configuration',body['metadata']['annotations'])
|
||||
self.assertNotIn('password',body['stringData']);self.assertEqual(body['stringData']['url'],issuer.URL)
|
||||
def test_silent_main_does_not_echo_invalid_contract_or_raw_child_errors(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
p=Path(tmp)/'contract.json';p.write_text('SECRET_CANARY')
|
||||
result=subprocess.run(['python3',str(Path(__file__).resolve().parents[1]/'scripts/provision_argocd_issue_core_repository.py'),'--contract',str(p),'--contract-sha256','a'*64,'--journal',str(Path(tmp)/'journal.json')],capture_output=True,text=True)
|
||||
self.assertEqual(result.returncode,1);self.assertEqual(result.stdout,'');self.assertEqual(result.stderr,'');self.assertEqual(json.loads((Path(tmp)/'journal.json').read_text())['failure'],'contract_changed')
|
||||
def test_synthetic_end_to_end_uses_one_unwrap_and_exact_destination(self):
|
||||
commands=[];public='ssh-ed25519 SYNTHETIC_PUBLIC fixture';private='SYNTHETIC_PRIVATE_CANARY';known='[forgejo.coulomb.social]:30022 ssh-ed25519 SYNTHETIC_HOST\n'
|
||||
contract=self.contract();contract['ssh_known_hosts_sha256']=__import__('hashlib').sha256(known.encode()).hexdigest()
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
journal=Path(tmp)/'journal.json';keydir=Path(tmp)/'keys';keydir.mkdir()
|
||||
class FakeTmp:
|
||||
def __enter__(self):return str(keydir)
|
||||
def __exit__(self,*args):return False
|
||||
def runner(argv,body=None,env=None):
|
||||
commands.append((argv,body,env));text=' '.join(argv)
|
||||
if 'get nodes' in text:return json.dumps({'items':[{'status':{'addresses':[{'address':'92.205.62.239'}]}}]})
|
||||
if 'get configmap' in text:return json.dumps({'data':{'ssh_known_hosts':known}})
|
||||
if 'get appproject' in text:return json.dumps({'spec':{'sourceRepos':[issuer.URL]}})
|
||||
if argv[:3]==['bao','token','capabilities']:return 'create, read, update'
|
||||
if argv[:3]==['bao','audit','list']:return json.dumps({'file/':{'type':'file'}})
|
||||
if 'get secret' in text:return '' if '--ignore-not-found' in text else 'secret/'+issuer.SECRET
|
||||
if argv[0]=='ssh-keygen':
|
||||
path=Path(argv[-1]);path.write_text(private);path.chmod(0o600);Path(str(path)+'.pub').write_text(public);return ''
|
||||
if argv[:3]==['bao','kv','put']:return json.dumps({'request_id':'write-fixture','data':{'version':1}})
|
||||
if argv[:3]==['bao','kv','get']:return json.dumps({'wrap_info':{'ttl':60,'token':'SYNTHETIC_WRAP','accessor':'fixture-accessor','creation_path':'platform/data/operators/argocd/repositories/rapp-issue-core'}})
|
||||
if argv[:2]==['bao','unwrap']:
|
||||
self.assertEqual(env['BAO_TOKEN'],'SYNTHETIC_WRAP');return json.dumps({'request_id':'unwrap-fixture','data':{'data':{'SSH_PRIVATE_KEY':private,'PROVIDER_KEY_ID':'7'}}})
|
||||
return 'secret/'+issuer.SECRET
|
||||
def capture(argv,body=None,env=None):
|
||||
commands.append((argv,body,env))
|
||||
if argv[:2]==['bao','list']:return subprocess.CompletedProcess(argv,2,'{}','')
|
||||
return subprocess.CompletedProcess(argv,2,'','Code: 400. Errors:\n\n* wrapping token is not valid or does not exist\n')
|
||||
def provider(token,method,path,payload=None,expected=(200,)):
|
||||
if method=='POST':
|
||||
self.assertEqual(payload,{'title':issuer.TITLE,'key':public,'read_only':True});return {'id':7,'key':public,'read_only':True}
|
||||
return [] if '/keys?' in path else {'full_name':issuer.REPO,'private':True}
|
||||
with patch.dict(os.environ,{'API_TOKEN':'SYNTHETIC_ADMIN'}),patch.object(issuer,'run',runner),patch.object(issuer,'captured',capture),patch.object(issuer,'api',provider),patch.object(issuer.tempfile,'TemporaryDirectory',lambda **kw:FakeTmp()):issuer.execute(contract,journal)
|
||||
receipt=json.loads(journal.read_text());self.assertEqual(receipt['state'],'delivered_not_adopted');self.assertNotIn(private,journal.read_text());self.assertNotIn('SYNTHETIC_WRAP',journal.read_text())
|
||||
self.assertEqual(sum(argv[:2]==['bao','unwrap'] for argv,body,env in commands),2);self.assertTrue(receipt['consumed_wrap_replay_denied_400']);self.assertEqual(receipt['wrap_creation_path'],'platform/data/operators/argocd/repositories/rapp-issue-core')
|
||||
for argv,body,env in commands:
|
||||
self.assertNotIn(private,' '.join(argv));self.assertNotIn('SYNTHETIC_WRAP',' '.join(argv));self.assertNotIn(' apply ',' '.join(argv));self.assertNotIn(' patch ',' '.join(argv))
|
||||
|
||||
def test_empty_metadata_list_404_is_accepted_other_errors_refused(self):
|
||||
with patch.object(issuer,'captured',return_value=subprocess.CompletedProcess([],2,'{}','')):self.assertEqual(issuer.metadata_children(),[])
|
||||
for value in [subprocess.CompletedProcess([],2,'{}','SECRET_CANARY'),subprocess.CompletedProcess([],1,'','Code: 403'),subprocess.CompletedProcess([],2,'','network unreachable')]:
|
||||
with patch.object(issuer,'captured',return_value=value):
|
||||
with self.assertRaisesRegex(issuer.Refused,'^custody_metadata_unverified$'):issuer.metadata_children()
|
||||
def test_replay_denial_requires_specific_400_not_transport_or_forbidden(self):
|
||||
good='Error unwrapping: Error making API request.\nCode: 400. Errors:\n\n* wrapping token is not valid or does not exist\n'
|
||||
for code,text,expected in [(2,good,True),(2,good.replace('400','403'),False),(2,'dial tcp: connection refused SECRET_CANARY',False),(1,good,False),(2,'Code: 400\n* unrelated failure',False)]:
|
||||
with patch.object(issuer,'captured',return_value=subprocess.CompletedProcess([],code,'',text)):self.assertEqual(issuer.replay_denied({}),expected)
|
||||
|
||||
def test_raw_command_output_timeout_and_error_suppressed(self):
|
||||
for result in [subprocess.CompletedProcess(['fixture'],1,'SECRET_CANARY','SECRET_CANARY'),subprocess.TimeoutExpired(['fixture'],60,output='SECRET_CANARY',stderr='SECRET_CANARY')]:
|
||||
with patch.object(issuer.subprocess,'run',side_effect=result if isinstance(result,Exception) else None,return_value=result):
|
||||
with self.assertRaisesRegex(issuer.Refused,'^command_unverified$'):issuer.run(['fixture'])
|
||||
|
||||
if __name__=='__main__':unittest.main()
|
||||
88
tests/test_public_transport.py
Normal file
88
tests/test_public_transport.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import copy
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
spec=importlib.util.spec_from_file_location('transport',Path(__file__).resolve().parents[1]/'scripts/argocd_public_transport.py')
|
||||
t=importlib.util.module_from_spec(spec);spec.loader.exec_module(t)
|
||||
|
||||
class TransportTests(unittest.TestCase):
|
||||
def plan(self):return json.loads((Path(__file__).resolve().parents[1]/'docs/evidence/2026-10-11-issue-core-transport/transport-plan.json').read_text())
|
||||
def objects(self,plan):
|
||||
result={}
|
||||
for item in plan['resources']:
|
||||
obj={'metadata':{'uid':item['uid'],'resourceVersion':'17','labels':{'unrelated':'retain'}},'data':{},'spec':{}}
|
||||
t.replace(obj,item['path'],copy.deepcopy(item['before']));result[item['kind']]=obj
|
||||
return result
|
||||
def runner(self,objects,calls,modify_dry=False):
|
||||
def run(args,body=None):
|
||||
calls.append((args,body))
|
||||
if args[:2]==['get','nodes']:return json.dumps({'items':[{'status':{'addresses':[{'address':'92.205.62.239'}]}}]})
|
||||
kind=args[3]
|
||||
if args[2]=='get':return json.dumps(objects[kind])
|
||||
obj=copy.deepcopy(objects[kind])
|
||||
for op in json.loads(body):
|
||||
if op['op']=='test':self.assertEqual(t.value(obj,op['path']),op['value'])
|
||||
else:t.replace(obj,op['path'],op['value'])
|
||||
if '--dry-run=server' in args:
|
||||
if modify_dry:obj['metadata']['labels']['unrelated']='changed'
|
||||
return json.dumps(obj)
|
||||
objects[kind]=obj;return kind+'/'+args[4]
|
||||
return run
|
||||
def receipt(self):return {'resources':[],'mutation_attempted':False}
|
||||
def test_dry_run_changes_no_live_values(self):
|
||||
plan=self.plan();objects=self.objects(plan);before=copy.deepcopy(objects);calls=[]
|
||||
with patch.object(t,'run',self.runner(objects,calls)):r=t.perform(plan,'dry-run',self.receipt())
|
||||
self.assertEqual(objects,before);self.assertTrue(r['passed']);self.assertFalse(r['mutation_attempted']);self.assertEqual(sum(args[2]=='patch' for args,body in calls if len(args)>2),2)
|
||||
def test_execute_and_guarded_rollback_preserve_other_fields(self):
|
||||
plan=self.plan();objects=self.objects(plan);before=copy.deepcopy(objects);calls=[];checkpoints=[];receipt=self.receipt()
|
||||
with patch.object(t,'run',self.runner(objects,calls)):
|
||||
t.perform(plan,'execute',receipt,lambda:checkpoints.append(copy.deepcopy(receipt)))
|
||||
for item in plan['resources']:self.assertEqual(t.value(objects[item['kind']],item['path']),item['after'])
|
||||
self.assertTrue(any(x.get('pending_target') for x in checkpoints))
|
||||
t.perform(plan,'verify',self.receipt());t.perform(plan,'rollback',self.receipt())
|
||||
self.assertEqual(objects,before)
|
||||
for args,body in calls:
|
||||
if body:
|
||||
ops=json.loads(body);self.assertEqual([op['op'] for op in ops],['test','test','test','replace']);self.assertEqual(ops[0]['path'],'/metadata/uid');self.assertEqual(ops[1]['path'],'/metadata/resourceVersion')
|
||||
def test_other_target_preimage_drift_stops_before_first_patch(self):
|
||||
plan=self.plan();objects=self.objects(plan);objects['appproject']['spec']['sourceRepos'].append('ssh://unreviewed/repo');calls=[]
|
||||
with patch.object(t,'run',self.runner(objects,calls)):
|
||||
with self.assertRaisesRegex(t.Refused,'^target_preimage_changed$'):t.perform(plan,'execute',self.receipt())
|
||||
self.assertFalse(any(args[2]=='patch' for args,body in calls if len(args)>2))
|
||||
def test_partial_second_write_is_recorded_and_rollback_can_restore_first(self):
|
||||
plan=self.plan();objects=self.objects(plan);before=copy.deepcopy(objects);calls=[];runner=self.runner(objects,calls);receipt=self.receipt();snapshots=[]
|
||||
def failing(args,body=None):
|
||||
if len(args)>3 and args[2]=='patch' and args[3]=='appproject' and '--dry-run=server' not in args:raise t.Refused('command_outcome_unverified')
|
||||
return runner(args,body)
|
||||
with patch.object(t,'run',failing):
|
||||
with self.assertRaises(t.Refused):t.perform(plan,'execute',receipt,lambda:snapshots.append(copy.deepcopy(receipt)))
|
||||
self.assertEqual(receipt['pending_target'],'appproject/railiance-tenants');self.assertTrue(receipt['resources'][0]['changed_verified']);self.assertTrue(snapshots[-1]['mutation_attempted'])
|
||||
with patch.object(t,'run',runner):t.perform(plan,'rollback',self.receipt())
|
||||
self.assertEqual(objects,before)
|
||||
|
||||
def test_target_uid_change_refused(self):
|
||||
plan=self.plan();objects=self.objects(plan);objects['configmap']['metadata']['uid']='foreign'
|
||||
with patch.object(t,'run',self.runner(objects,[])):
|
||||
with self.assertRaisesRegex(t.Refused,'^target_uid_changed$'):t.perform(plan,'execute',self.receipt())
|
||||
def test_server_admission_extra_change_refused(self):
|
||||
plan=self.plan();objects=self.objects(plan);before=copy.deepcopy(objects)
|
||||
with patch.object(t,'run',self.runner(objects,[],True)):
|
||||
with self.assertRaisesRegex(t.Refused,'^dry_run_modified_other_fields$'):t.perform(plan,'execute',self.receipt())
|
||||
self.assertEqual(objects,before)
|
||||
def test_broader_repo_or_host_replacement_refused(self):
|
||||
for kind in ('host','repo'):
|
||||
plan=self.plan()
|
||||
if kind=='host':plan['resources'][0]['after']='unreviewed replacement\n'
|
||||
else:plan['resources'][1]['after'].append('ssh://forgejo.coulomb.social/coulomb/*')
|
||||
with self.assertRaises(t.Refused):t.validate(plan)
|
||||
def test_wrong_estate_and_raw_canary_refused(self):
|
||||
with patch.object(t,'run',return_value=json.dumps({'items':[{'status':{'addresses':[{'address':'192.0.2.8'}]}}]})) as run:
|
||||
with self.assertRaisesRegex(t.Refused,'^wrong_estate$'):t.perform(self.plan(),'execute',self.receipt())
|
||||
self.assertEqual(run.call_count,1)
|
||||
with patch.object(t.subprocess,'run',return_value=subprocess.CompletedProcess([],1,'SECRET_CANARY','SECRET_CANARY')):
|
||||
with self.assertRaisesRegex(t.Refused,'^command_outcome_unverified$'):t.run(['get','nodes'])
|
||||
|
||||
if __name__=='__main__':unittest.main()
|
||||
114
tests/test_workload_adoption_proof.py
Normal file
114
tests/test_workload_adoption_proof.py
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
import copy
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
SCRIPTS = Path(__file__).resolve().parents[1] / 'scripts'
|
||||
if not SCRIPTS.exists(): SCRIPTS = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(SCRIPTS))
|
||||
import workload_adoption_proof as proof
|
||||
|
||||
NOW = datetime(2026, 10, 11, 20, 0, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def observation():
|
||||
c = proof.CONTRACT
|
||||
old = (NOW - timedelta(days=2)).isoformat()
|
||||
settings = {'method': 'annotation', 'controller_version': 'v3.5.3', 'installation_id_absent': True}
|
||||
return {'captured_at': NOW.isoformat(), 'metadata_captured_at': NOW.isoformat(), 'estate_ip': c['estate_ip'], 'tracking_settings': settings,
|
||||
'application': {'name': c['application'], 'uid': 'application-uid', 'project': c['project'], 'destination': c['destination'],
|
||||
'source': {'repoURL': c['repo_url'], 'path': c['source_path'], 'targetRevision': c['revision']}, 'revision': c['revision'],
|
||||
'automated_present': False, 'pending_operation': False, 'sync': 'Synced', 'health': 'Healthy', 'condition_types': [],
|
||||
'operation': {'phase': 'Succeeded', 'startedAt': old, 'finishedAt': old}, 'sync_prune': False,
|
||||
'sync_strategy_apply': True, 'sync_strategy_force': False, 'operation_revision': c['revision'], 'resources': [{**r, 'status': 'Synced'} for r in c['resources']]},
|
||||
'ready_checks': [{**check, 'uid': 'resource-uid', 'tracking_matches': True,
|
||||
'ready': [{'status': 'True', 'lastTransitionTime': old}], 'generation': 2, 'observed_generation': 2,
|
||||
'replicas': 1, 'available_replicas': 1, 'instances': 1, 'ready_instances': 1} for check in c['ready_checks']],
|
||||
'live_hooks': [], 'public_health': {'url': c['health_url'], 'status': 200},
|
||||
'pinned_render_diff': {'revision': c['revision'], 'path': c['source_path'], 'client_side_exit_code': 0,
|
||||
'render_resource_count': len(c['resources']), 'skipped_hooks': sorted(c['hooks']), 'output_suppressed': True,
|
||||
'render_sha256': 'a' * 64, 'normalized_render_sha256': 'b' * 64}}
|
||||
|
||||
|
||||
def test_verified_receipt_is_manual_adoption_only():
|
||||
receipt = proof.verify(observation(), proof.CONTRACT, now=NOW)
|
||||
assert receipt['status'] == 'verified'
|
||||
assert receipt['resource_count'] == 5
|
||||
assert receipt['ready_count'] == 3
|
||||
assert receipt['proving_seconds'] == 172800
|
||||
assert receipt['native_mutation'] is False
|
||||
assert receipt['child_automation_enabled'] is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize('field,value', [('automated_present', True), ('pending_operation', True), ('sync_prune', True),
|
||||
('sync_strategy_apply', False), ('sync_strategy_force', True), ('revision', 'f'*40), ('health', 'Progressing'), ('condition_types', ['ComparisonError'])])
|
||||
def test_wrong_runtime_gate_fails(field, value):
|
||||
obj = observation(); obj['application'][field] = value
|
||||
with pytest.raises(ValueError): proof.verify(obj, proof.CONTRACT, now=NOW)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('mutation', ['missing-resource', 'extra-hook-resource', 'resource-not-synced', 'hook-live',
|
||||
'short-proving', 'stale-observation', 'future-observation', 'metadata-stale', 'client-diff', 'missing-check', 'ready-false',
|
||||
'new-ready-transition', 'deployment-generation', 'deployment-replicas', 'cnpg-instances', 'tracking', 'health-url', 'health-status'])
|
||||
def test_missing_or_wrong_proof_fails(mutation):
|
||||
obj = observation()
|
||||
if mutation == 'missing-resource': obj['application']['resources'].pop()
|
||||
elif mutation == 'extra-hook-resource': obj['application']['resources'].append({'group': 'batch', 'kind': 'Job', 'namespace': 'target-revenue', 'name': proof.CONTRACT['hooks'][0], 'status': 'Synced'})
|
||||
elif mutation == 'resource-not-synced': obj['application']['resources'][0]['status'] = 'OutOfSync'
|
||||
elif mutation == 'hook-live': obj['live_hooks'] = [proof.CONTRACT['hooks'][0]]
|
||||
elif mutation == 'short-proving': obj['application']['operation']['finishedAt'] = (NOW-timedelta(hours=23)).isoformat()
|
||||
elif mutation == 'stale-observation': obj['captured_at'] = (NOW-timedelta(minutes=6)).isoformat()
|
||||
elif mutation == 'future-observation': obj['captured_at'] = (NOW+timedelta(seconds=1)).isoformat()
|
||||
elif mutation == 'metadata-stale': obj['metadata_captured_at'] = (NOW-timedelta(minutes=6)).isoformat()
|
||||
elif mutation == 'client-diff': obj['pinned_render_diff']['client_side_exit_code'] = 1
|
||||
elif mutation == 'missing-check': obj['ready_checks'].pop()
|
||||
elif mutation == 'ready-false': obj['ready_checks'][1]['ready'][0]['status'] = 'False'
|
||||
elif mutation == 'new-ready-transition': obj['ready_checks'][1]['ready'][0]['lastTransitionTime'] = NOW.isoformat()
|
||||
elif mutation == 'deployment-generation': obj['ready_checks'][0]['observed_generation'] = 1
|
||||
elif mutation == 'deployment-replicas': obj['ready_checks'][0]['available_replicas'] = 0
|
||||
elif mutation == 'cnpg-instances': obj['ready_checks'][2]['ready_instances'] = 0
|
||||
elif mutation == 'tracking': obj['ready_checks'][0]['tracking_matches'] = False
|
||||
elif mutation == 'health-url': obj['public_health']['url'] = 'https://unexpected.test/healthz'
|
||||
elif mutation == 'health-status': obj['public_health']['status'] = 503
|
||||
with pytest.raises(ValueError): proof.verify(obj, proof.CONTRACT, now=NOW)
|
||||
|
||||
|
||||
def sources():
|
||||
docs = []
|
||||
for r in proof.CONTRACT['resources']:
|
||||
docs.append({'apiVersion': r['group']+'/v1' if r['group'] else 'v1', 'kind': r['kind'],
|
||||
'metadata': {'name': r['name'], 'namespace': r['namespace']}})
|
||||
for name in proof.CONTRACT['hooks']:
|
||||
docs.append({'apiVersion': 'batch/v1', 'kind': 'Job', 'metadata': {'name': name, 'namespace': 'target-revenue', 'annotations': {'argocd.argoproj.io/hook': 'Sync'}}})
|
||||
return docs
|
||||
|
||||
|
||||
def test_normalization_skips_hooks_and_only_injects_tracking_metadata():
|
||||
docs = sources(); original = copy.deepcopy(docs)
|
||||
settings = observation()['tracking_settings']
|
||||
normalized = proof.normalize(docs, proof.CONTRACT, settings)
|
||||
assert len(normalized) == 5
|
||||
assert docs == original
|
||||
assert not any(r['kind'] == 'Job' for r in normalized)
|
||||
assert normalized[0]['metadata']['annotations']['argocd.argoproj.io/tracking-id'] == 'target-revenue:/Service:target-revenue/target-revenue'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('bad', ['secret', 'extra-resource', 'wrong-hook', 'missing-hook', 'duplicate-resource', 'conflicting-tracking'])
|
||||
def test_normalization_fails_closed(bad):
|
||||
docs = sources()
|
||||
if bad == 'secret': docs.append({'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'secret', 'namespace': 'target-revenue'}})
|
||||
elif bad == 'extra-resource': docs.append({'apiVersion': 'v1', 'kind': 'Service', 'metadata': {'name': 'extra', 'namespace': 'target-revenue'}})
|
||||
elif bad == 'wrong-hook': docs[-1]['metadata']['annotations']['argocd.argoproj.io/hook'] = 'PreSync'
|
||||
elif bad == 'missing-hook': docs.pop()
|
||||
elif bad == 'duplicate-resource': docs.append(copy.deepcopy(docs[0]))
|
||||
elif bad == 'conflicting-tracking': docs[0]['metadata']['annotations'] = {'argocd.argoproj.io/tracking-id': 'other'}
|
||||
with pytest.raises(ValueError): proof.normalize(docs, proof.CONTRACT, observation()['tracking_settings'])
|
||||
|
||||
|
||||
def test_kubectl_stream_and_list_decoding():
|
||||
docs = sources()
|
||||
assert proof.decode_objects(('\n'.join(json.dumps(r) for r in docs)).encode()) == docs
|
||||
assert proof.decode_objects(json.dumps({'kind': 'List', 'items': docs}).encode()) == docs
|
||||
|
|
@ -268,36 +268,25 @@ proving period without changing sync/automation policy or using a new human gate
|
|||
|
||||
```task
|
||||
id: RPF-WP-0044-T04
|
||||
status: todo
|
||||
status: wait
|
||||
needs_human: true
|
||||
intervention_note: "Orange attended Platform OpenBao OIDC/MFA login for the already D2-approved exact repository reader; no second source-scope approval."
|
||||
decision_id: 89f1058f-33bc-47a8-8a49-0b44d426c67e
|
||||
priority: high
|
||||
state_hub_task_id: "0e619658-0453-51ed-b9a0-bed28b5544fd"
|
||||
```
|
||||
|
||||
2026-10-11: requalified after founder decision D2 (ArgoCD owns production) resolved — see the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md.
|
||||
D2 already authorizes this exact private repository read-only reader and the ordered adoption. No renewed founder go-ahead is required. The remaining human act is the existing Platform OpenBao attended OIDC/MFA login; route it through the single attended-window checklist.
|
||||
|
||||
**Waits on the founder's go-ahead**, and on two preconditions:
|
||||
Source preparation now disables wrapper direct deployment/rollback and verifies Argo ownership using the exact HTTPS or SSH source URL. The fixed credential executor is `scripts/provision_argocd_issue_core_repository.py`, reviewed in `docs/evidence/2026-10-11-issue-core-credential/owner-review.json`. It creates one repository-only read-only SSH deploy key, stores it at `platform/operators/argocd/repositories/rapp-issue-core`, response-wraps for 60 seconds, unwraps once, proves consumed-token denial, and creates only `argocd/rapp-issue-core-repository`. Tests and installed Bao synthetic checks pass; native issuance/adoption have not run. The contract example is incomplete and cannot authorize execution.
|
||||
|
||||
- `rapp-issue-core` agrees (hub message) that ArgoCD owns production and its
|
||||
`make deploy` stops being a production path; it would fight self-heal.
|
||||
Image releases become commits to `manifests/30-runtime.yaml`.
|
||||
- A CCR for the ArgoCD repository credential, source at
|
||||
`platform/operators/argocd/repositories/rapp-issue-core`, read-only deploy
|
||||
token. Applied with `make argocd-repo-apply`; no value is handled in this
|
||||
plan.
|
||||
|
||||
Zero diff against live for both the raw `manifests/` and the rendered output
|
||||
(digest `sha256:a56c80cc…b92cf`). All kinds are in the `railiance-tenants`
|
||||
whitelist. Live check: `rapp-issue-core make verify-live`. Workload restore:
|
||||
`rapp-issue-core make rollback`.
|
||||
|
||||
2026-10-03: human gate routed to hub decision 89f1058f-33bc-47a8-8a49-0b44d426c67e (D2: ArgoCD owns production: go-ahead sequence for railiance-apps-root, issue-core, policy-nexus and activity-core automation) by CUST-WP-0075; resolving that decision raises C-40 here.
|
||||
Public host trust and exact project admission are prepared in owner source and the bounded `scripts/argocd_public_transport.py`; its server dry-run passed. Preserve each exact attempt and any partial failure. Finish those prerequisites, seal the owner-reviewed contract, use the attended login, then pin the newly committed wrapper source in the Application, inspect client-side native diff, manually sync with prune/force disabled, and run the bounded owner verifier. Image releases and rollback use Git revisions plus Argo; direct `make rollback` is disabled. Never adopt the stale wrapper revision that predates the ExternalSecret metadata guard.
|
||||
|
||||
## Adopt target-revenue
|
||||
|
||||
```task
|
||||
id: RPF-WP-0044-T05
|
||||
status: todo
|
||||
status: done
|
||||
decision_id: 89f1058f-33bc-47a8-8a49-0b44d426c67e
|
||||
priority: medium
|
||||
state_hub_task_id: "d418068a-6fb0-5416-aac5-d23c93924d9c"
|
||||
|
|
@ -321,6 +310,24 @@ target-revenue commit.
|
|||
|
||||
2026-10-03: human gate routed to hub decision 89f1058f-33bc-47a8-8a49-0b44d426c67e (D2: ArgoCD owns production: go-ahead sequence for railiance-apps-root, issue-core, policy-nexus and activity-core automation) by CUST-WP-0075; resolving that decision raises C-40 here.
|
||||
|
||||
|
||||
2026-10-11: **manual adoption proving complete**. Fresh read-only proof at 2026-10-11T19:51:12.812423+00:00
|
||||
verified exact pin `a3a8a27a8cda32ae3c7b55353ee16a131c50a0a0`, manual apply strategy,
|
||||
prune/force off and no pending operation. The last successful pinned apply finished
|
||||
2026-09-28T14:23:27Z; 1142865 seconds of proving exceeds 24 hours.
|
||||
All five non-hook objects are tracked and Synced; Deployment Available (generation observed),
|
||||
ExternalSecret Ready and CNPG Ready (1/1). ArgoCD remains Synced/Healthy; normalized native
|
||||
client-side diff is zero and `https://revenue.coulomb.social/healthz` returns 200. Both declared
|
||||
Sync-hook Jobs are absent and excluded from the apply-strategy diff. No live mutation or
|
||||
Secret read was performed. Receipt: `docs/evidence/2026-10-11-target-revenue-adoption-proof/receipt.json`;
|
||||
sanitized observation alongside it. Reusable check: `scripts/workload_adoption_proof.py`;
|
||||
35 new failure/normalization tests plus 13 existing OpenBao adoption tests pass.
|
||||
|
||||
This closes T05 adoption/proving only. Child automated sync stays off. The existing
|
||||
target-revenue owner handoff for hook re-execution safety remains a prerequisite to future
|
||||
automation; it is not silently resolved and no new residual is invented here. The observed
|
||||
OrphanedResourceWarning is non-blocking; other Application condition types fail verification.
|
||||
|
||||
## Adopt external-secrets (highest risk)
|
||||
|
||||
```task
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ status: blocked
|
|||
owner: codex
|
||||
topic_slug: railiance
|
||||
created: "2026-09-27"
|
||||
updated: "2026-10-03"
|
||||
updated: "2026-10-11"
|
||||
state_hub_workstream_id: "b8cf2fc2-60c2-5d4e-a60a-55f1304d9f54"
|
||||
---
|
||||
|
||||
|
|
@ -40,9 +40,9 @@ health, resource inventory, schedules and original deployment generations.
|
|||
|
||||
```task
|
||||
id: RPF-WP-0048-T02
|
||||
status: todo
|
||||
status: wait
|
||||
decision_id: 89f1058f-33bc-47a8-8a49-0b44d426c67e
|
||||
depends_on: [ACTIVITY-WP-0041]
|
||||
depends_on: [ACTIVITY-WP-0041-T03]
|
||||
priority: high
|
||||
state_hub_task_id: "63b44949-39f8-52fd-ab63-e618b67ef992"
|
||||
```
|
||||
|
|
@ -58,6 +58,36 @@ activating bounded routine promotion. Destructive pruning remains disabled.
|
|||
|
||||
2026-10-03: human gate routed to hub decision 89f1058f-33bc-47a8-8a49-0b44d426c67e (D2: ArgoCD owns production: go-ahead sequence for railiance-apps-root, issue-core, policy-nexus and activity-core automation) by CUST-WP-0075; resolving that decision raises C-40 here.
|
||||
|
||||
2026-10-11: qualified once as an **owner-proof wait**, not a founder gate.
|
||||
`scripts/activity_promotion_readiness.py` now checks the exact live source pin
|
||||
`a86a143`, local estate/cluster identity, narrow AppProject, nine tracked runtime
|
||||
objects, manual/no-prune policy, successful Oct4 sync, elapsed 24h eligibility and
|
||||
all three Deployment generations/readiness. Native read-only checks pass. Elapsed
|
||||
time and healthy snapshots are explicitly not gap-sensitive continuous-observation
|
||||
proof. The gate requires authenticated owner acceptance for the dedicated release
|
||||
principal, custody/revocation/denials, independently trusted receipts, real
|
||||
continuous sampling, source CAS/single-flight/restart, retention, Temporal/audit
|
||||
sink/invariants and successful failed-health rollback before returning ready.
|
||||
Nine fixture tests (including forged trust and substantive scope failures) pass.
|
||||
No release identity or trusted signer is created by this gate.
|
||||
|
||||
Owner evidence is still absent: `activity-core/docs/release-broker.md` explicitly
|
||||
records its modules as not activated in production, and
|
||||
`activity-core/docs/gitops-release.md` retains the actual release-identity and
|
||||
rollback requirements. ACTIVITY-WP-0042's accepted scheduling boundary remains
|
||||
scheduling-only; `docs/platform-recovery-scheduling-plan.md` does not admit a
|
||||
release principal or new mutating recovery operation. D2 resolves the founder
|
||||
question but does not supply those machine proofs. Therefore wait on the existing
|
||||
ACTIVITY-WP-0041-T03 once; no new `needs_human` flag or per-item permission request.
|
||||
|
||||
Cross-owner case and checkpoint:
|
||||
`docs/evidence/2026-10-11-activity-promotion-readiness/coordination-case.json`.
|
||||
Read-only receipt: the same directory's `readiness.json` and `validation.json`.
|
||||
No worker was injected or foreign task changed. Next action: owner supplies
|
||||
authenticated native acceptance and trusted issuer binding, then rerun the gate
|
||||
before bounded dispatcher activation. No child source/Argo automation candidate
|
||||
was changed, and this wait does not authorize direct automated sync.
|
||||
|
||||
## Make registry retention aware of digest-pinned releases
|
||||
|
||||
```task
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue