Compare commits
30
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5528868ef9 | ||
|
|
9303a5d684 | ||
|
|
8814dff202 | ||
|
|
ff6743e851 | ||
|
|
ebd7439874 | ||
|
|
c41e278c59 | ||
|
|
1071979079 | ||
|
|
9a158fcab9 | ||
|
|
8b7f9d9028 | ||
|
|
3aa1872084 | ||
|
|
f6bef90633 | ||
|
|
c2eed5b379 | ||
|
|
6e8212608e | ||
|
|
e6a53888fe | ||
|
|
2bef44f6bc | ||
|
|
a96f36bf72 | ||
|
|
4e6ef70029 | ||
|
|
80feef309f | ||
|
|
a5489f1b67 | ||
|
|
312d3b4830 | ||
|
|
ac2dc5c30e | ||
|
|
5ef261b265 | ||
|
|
3a22c3c7d8 | ||
|
|
1c9f28a613 | ||
|
|
9514062cf3 | ||
|
|
a701a32cb4 | ||
|
|
45f3226662 | ||
|
|
975d239d40 | ||
|
|
3bc44accb5 | ||
|
|
3304d87872 |
+83
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"approval_request_branch": "governance/request/integration-refresh-control-plane-deployment-20260728T093428Z",
|
||||
"approval_type": "CONTROL_PLANE_DEPLOYMENT_APPROVAL_REQUEST",
|
||||
"authorization_lineage": {
|
||||
"execution_renewal_merge_oid": "3a22c3c7d86930efd4bd93ac05d80ad322050cb9",
|
||||
"grant_id": "grant-11354281aafdd074df5899a3925dd2a0370cffa9edb06f5c7d7227ce8de00d91",
|
||||
"source_mapping_approval_merge_oid": "9514062cf33e45941abe4a8c98377e0713b0b3f6"
|
||||
},
|
||||
"deployment_status": "NOT_EXECUTED",
|
||||
"evidence": {
|
||||
"control_plane_artifact": {
|
||||
"path": "/var/lib/aios-integration-refresh-source-writer-v2/artifacts/control-plane-patch-112d995545e3e79126a062b2643a9f0c17ae7bd4.tar",
|
||||
"sha256": "bf101ade1aaf956f40c0bba74ed4b347f5f38d0de6a0c1b7694ae107c4b43c14",
|
||||
"size_bytes": 13363200
|
||||
},
|
||||
"rendered_systemd_config_sha256": "182a3cba48e752798cf24323a8c0249c53d7386371388a8db0435a29c43dc969",
|
||||
"rollback_artifact": {
|
||||
"path": "/var/lib/aios-gitops/runtime/tasks/AIOS-INTEGRATION-REFRESH-PROVISIONER-IMPLEMENTATION-20260726/implementation/rollback-to-85426f4f570e4d40b212318d47e52e4aed5cb06a.tar",
|
||||
"sha256": "f12a2a95e475da5d02eaff99e93615cb0186019ca30edae013ab054a2c6418ce"
|
||||
},
|
||||
"source_map": {
|
||||
"path": "docs/autonomous-release/INTEGRATION_REFRESH_SOURCE_MAP_20260728.json",
|
||||
"sha256": "01ddd56cfa2a525559e2d174cf7df117a9465ffce44ef25bc7a15b8e74d06bf5"
|
||||
},
|
||||
"test_evidence": {
|
||||
"collection_errors": 0,
|
||||
"path": "/var/lib/aios-gitops/runtime/tasks/AIOS-INTEGRATION-REFRESH-PROVISIONER-IMPLEMENTATION-20260726/implementation/test-evidence.json",
|
||||
"sha256": "d8933553f56462a3f1b3040dec989ff0adf717a17728d55ba0fe9e127d93c206",
|
||||
"tests_failed": 0,
|
||||
"tests_passed": 41
|
||||
}
|
||||
},
|
||||
"execution_conditions": {
|
||||
"candidate_only": true,
|
||||
"integration_unchanged": true,
|
||||
"main_unchanged": true,
|
||||
"no_credentials_or_secrets_in_request": true,
|
||||
"production_baseline_unchanged": true,
|
||||
"registry_unchanged": true
|
||||
},
|
||||
"expected_governance_base_oid": "3a22c3c7d86930efd4bd93ac05d80ad322050cb9",
|
||||
"expires_at": "2026-07-28T21:34:28Z",
|
||||
"implementation": {
|
||||
"commit_oid": "112d995545e3e79126a062b2643a9f0c17ae7bd4",
|
||||
"tree_oid": "4ed88399fb8e7e4c235413ebbd783c9b6ad3c624"
|
||||
},
|
||||
"implementation_branch": "patch/integration-refresh-grant-provisioner-20260726",
|
||||
"issued_at": "2026-07-28T09:34:28Z",
|
||||
"prohibited_operations": [
|
||||
"production-baseline.write",
|
||||
"production-tag.write",
|
||||
"main.write",
|
||||
"integration.write",
|
||||
"registry.write",
|
||||
"force-push",
|
||||
"direct-main-push",
|
||||
"direct-tag-write"
|
||||
],
|
||||
"repository": "aios/aios",
|
||||
"request_status": "PENDING_HUMAN_APPROVAL",
|
||||
"requested_operations": [
|
||||
"control-plane.candidate.deploy",
|
||||
"control-plane.candidate.verify",
|
||||
"control-plane.candidate.rollback"
|
||||
],
|
||||
"required_controls": [
|
||||
"Two distinct human approvals, one Codex and one GPT, both APPROVED against the same current approval-request PR head.",
|
||||
"Re-verify artifact, test evidence, source map, rendered systemd configuration, and rollback artifact paths and SHA-256 digests immediately before execution.",
|
||||
"AIOS_GITOPS_WRITES_ENABLED=false throughout candidate deployment, verification, and rollback.",
|
||||
"No force push, direct main write, or direct tag write."
|
||||
],
|
||||
"required_reviewers": [
|
||||
"codex",
|
||||
"gpt"
|
||||
],
|
||||
"schema": "aios.control-plane-deployment-approval-request/v1",
|
||||
"target_governance": {
|
||||
"approved_base_oid": "3a22c3c7d86930efd4bd93ac05d80ad322050cb9",
|
||||
"binding_rule": "Human approvals must bind to the same approval-request PR head; its first parent must equal the active governance base."
|
||||
},
|
||||
"task_id": "AIOS-INTEGRATION-REFRESH-PROVISIONER-IMPLEMENTATION-20260726",
|
||||
"validity_hours": 12
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"schema": "aios.governance-approval/v1",
|
||||
"approval_type": "TASK_SCOPED_GOVERNANCE_BOOTSTRAP_POLICY_ACTIVATION",
|
||||
"approval_plan_id": "AIOS-TSG-BS-PHASE-A-RENEWAL-20260731T081147Z",
|
||||
"task_id": "AIOS-V2R1C4-TWO-STAGE-OWNER-CAMPAIGN-V2-PHASE-A-RENEWAL",
|
||||
"repository": "aios/aios",
|
||||
"issued_at": "2026-07-31T08:11:47Z",
|
||||
"expires_at": "2026-07-31T20:11:47Z",
|
||||
"expected_governance_base_oid": "ebd74398743e36523b593fcc395b73945d4924c5",
|
||||
"expected_governance_base_tree_oid": "ad1255a1127204fad1d1208d8f31dc94fa64e6e1",
|
||||
"approval_request_branch": "governance/request/two-stage-owner-campaign-v2-phase-a-renewal-20260731T081147Z",
|
||||
"request_head_binding": "GITEA_EXACT_HEAD_REVIEW_POST_COMMIT",
|
||||
"human_authorizer": "owner",
|
||||
"authorization_mode": "GITEA_EXACT_HEAD_REVIEW",
|
||||
"required_human_approvals": 1,
|
||||
"allowed_human_approvers": [
|
||||
"owner"
|
||||
],
|
||||
"require_latest_head_approval": true,
|
||||
"dismiss_stale_approvals": true,
|
||||
"auto_merge_after_machine_gates": true,
|
||||
"phase_a_scope": {
|
||||
"schema_id": "TASK_SCOPED_GOVERNANCE_BOOTSTRAP",
|
||||
"schema_version": 1,
|
||||
"activate_schema_and_validator_only": true,
|
||||
"phase_b_release_authorized": false,
|
||||
"publisher_authorized": false,
|
||||
"writer_authorized": false,
|
||||
"credential_projection_authorized": false,
|
||||
"tag_authorized": false,
|
||||
"registry_authorized": false,
|
||||
"baseline_authorized": false,
|
||||
"production_deployment_authorized": false
|
||||
},
|
||||
"content_bindings": {
|
||||
"schema_document": {
|
||||
"path": "services/aios-single-owner-governance/task-scoped-governance-bootstrap-v1.schema.json",
|
||||
"blob_oid": "9b117667c0d98f995ac059b0e1b5ffed06ca6444",
|
||||
"sha256": "6ec912a96c8bac5c0d3c0a017abc078cb9567ed54dcb20bb0eadb5926ac54fdd"
|
||||
},
|
||||
"validator_policy": {
|
||||
"path": "services/aios-single-owner-governance/aios_single_owner_governance/task_scoped_bootstrap.py",
|
||||
"blob_oid": "c18d15f31340f19fc5da87944901bdfb867ab6dc",
|
||||
"sha256": "de11123b5cd9ccdad4961f1d5d084a8c4a1b2270ce793652ab9c50bb95694eac"
|
||||
},
|
||||
"evidence_manifest": {
|
||||
"path": "evidence/governance-policy/AIOS-TWO-STAGE-OWNER-CAMPAIGN-V2-PHASE-A-20260730T023551Z.json",
|
||||
"blob_oid": "c3d215b3ea525a25de514c32c5aad91d47e140b4",
|
||||
"sha256": "1b0a993940b41a4b16724b557f6abd8b6cebb56dc64cc8f813bd605b6a3399c2"
|
||||
},
|
||||
"checksums_manifest": {
|
||||
"path": "evidence/governance-policy/AIOS-TWO-STAGE-OWNER-CAMPAIGN-V2-PHASE-A-20260730T023551Z.sha256",
|
||||
"blob_oid": "45ed084234dae6a00e69a41cd4f716f46a97d9fb",
|
||||
"sha256": "51c4b029c0792dc98d41f754e484ba3861cf7604443d9c5f289b3b0af768a41a"
|
||||
}
|
||||
},
|
||||
"offline_candidate_bindings": {
|
||||
"schema_seed_commit_oid": "8da47545a507cb798d3d6ab30ff8552a2d0a5ecf",
|
||||
"schema_seed_tree_oid": "991e68577a469657c1c4996814b6634ce5ed752f",
|
||||
"implementation_commit_oid": "a637a9dbb6e0021cb7dcf0d6b0ca00dfe10dce38",
|
||||
"implementation_tree_oid": "bf6e0c16fcad7515465bec4aefdd2e77b1e9bef0",
|
||||
"evidence_closure_commit_oid": "1e4885197f508f7921b148d71e545b3de0029b0a",
|
||||
"evidence_closure_tree_oid": "e55cf9ef19d1c6804f161de54fd3395a06149807",
|
||||
"source_diff_sha256": "c06d002db06da6cbf16f9c8321f1acbfd431f009b5127a13fd2cb5d63b278867",
|
||||
"source_pathset_sha256": "5061402d516c0e9413208ce64e2b52f77d224e6eec1f7b07c5b3a10b58df5eaf",
|
||||
"implementation_bundle_sha256": "15711b225b9ef41cca0588d21b2b95d7a95ac155efc27fcb8772c435fcdab147",
|
||||
"evidence_bundle_sha256": "ec97b8460151fc0e7e7ea9ae044ca13a1543c4b79c41a437ef087d3b1c271857"
|
||||
},
|
||||
"runtime_dependency_bindings": {
|
||||
"handoff_zip_sha256": "668239a549b4a2b2094fe9b5104b84703fe8ed75011fd8ed71ed3958a6dc0706",
|
||||
"installed_wheel_sha256": "abbf616366eb3467d4e651bb5306e22ae5e931b613df5362a0892935c4a1c0c7",
|
||||
"anchor_service": "aios-campaign-anchor.service",
|
||||
"anchor_uid": 988,
|
||||
"runner_uid": 986,
|
||||
"anchor_database": "/var/lib/aios-campaign-anchor/anchor.sqlite3",
|
||||
"anchor_socket": "/run/aios-campaign-anchor/anchor.sock",
|
||||
"AIOS_GITOPS_WRITES_ENABLED": false
|
||||
},
|
||||
"required_machine_status_contexts": [
|
||||
"governance/schema-contract",
|
||||
"governance/validator-security",
|
||||
"governance/fault-injection",
|
||||
"governance/runtime-isolation",
|
||||
"governance/authority-mirror"
|
||||
],
|
||||
"allowed_operations_after_exact_head_owner_approval": [
|
||||
"observe_governance_pr",
|
||||
"wait_governance_gates",
|
||||
"observe_governance_merge",
|
||||
"observe_validator_activation",
|
||||
"complete_policy_activation"
|
||||
],
|
||||
"denied_operations": [
|
||||
"main.write",
|
||||
"integration.write",
|
||||
"tag.write",
|
||||
"registry.write",
|
||||
"baseline.write",
|
||||
"production.deploy",
|
||||
"force_push",
|
||||
"history_rewrite",
|
||||
"governance.direct_push",
|
||||
"approval.impersonate",
|
||||
"publisher.create",
|
||||
"writer.create",
|
||||
"credential.project",
|
||||
"phase_b.execute"
|
||||
],
|
||||
"merge_constraints": {
|
||||
"allowed_modes": [
|
||||
"FAST_FORWARD_TO_APPROVED_HEAD",
|
||||
"EXACT_TWO_PARENT_MERGE_WITH_APPROVED_TREE"
|
||||
],
|
||||
"force": false,
|
||||
"plus_refspec": false,
|
||||
"tags": false,
|
||||
"prune": false
|
||||
},
|
||||
"governance_declarations": {
|
||||
"AIOS_GITOPS_WRITES_ENABLED": false,
|
||||
"force_push_used": false,
|
||||
"tag_rewritten": false,
|
||||
"direct_main_push": false,
|
||||
"secrets_committed": false,
|
||||
"owner_approval_respected": true
|
||||
}
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
{
|
||||
"approval_type": "TASK_STATUS_MERGE_BROKER_BOOTSTRAP_RFC3339_FIX",
|
||||
"schema_version": 2,
|
||||
"repository": "aios/aios",
|
||||
"expected_governance_base_oid": "8814dff20222625b60454c613b61ccf15fa20e51",
|
||||
"request_branch": "patch/codex/AIOS-V2R1C4-BOOTSTRAP-RFC3339-OFFSET-FIX",
|
||||
"target_ref": "refs/heads/governance/approvals",
|
||||
"issued_at": "2026-08-01T02:50:00Z",
|
||||
"expires_at": "2026-08-01T14:50:00Z",
|
||||
"required_human_approvals": 1,
|
||||
"allowed_human_approvers": ["owner"],
|
||||
"require_latest_head_approval": true,
|
||||
"approval_scopes": [
|
||||
"BOOTSTRAP_FIX_APPROVAL",
|
||||
"GITEA_INTERNAL_TOKEN_ROTATION_APPROVAL"
|
||||
],
|
||||
"supersedes": {
|
||||
"pr_number": 37,
|
||||
"head_oid": "57697d81cf3d1fef23798ec390418efe8b64f4e9",
|
||||
"tree_oid": "b58af1925318a0eefa6cc709e8d52ba57bb20d7f",
|
||||
"reason": "APPROVED_BOOTSTRAP_RUNTIME_CONTRACT_MISMATCH",
|
||||
"old_review_reusable": false,
|
||||
"old_pr_immutable": true
|
||||
},
|
||||
"exact_head_binding": {
|
||||
"method": "GITEA_EXACT_HEAD_OWNER_REVIEW_AND_PR_METADATA",
|
||||
"head_oid_resolved_after_commit": true,
|
||||
"tree_oid_resolved_after_commit": true,
|
||||
"manifest_blob_oid_resolved_after_commit": true,
|
||||
"manifest_sha256_resolved_after_commit": true,
|
||||
"head_change_invalidates_review": true
|
||||
},
|
||||
"timestamp_contract": {
|
||||
"id": "RFC3339_OFFSET_AWARE_V2",
|
||||
"canonical_timezone": "UTC",
|
||||
"raw_timestamp_preserved": true,
|
||||
"naive_datetime_allowed": false,
|
||||
"accepted_timezone_forms": ["Z", "+HH:MM", "-HH:MM"],
|
||||
"fractional_seconds_allowed": true,
|
||||
"lowercase_z_allowed": false,
|
||||
"expiry_boundary": "REVIEW_MUST_BE_STRICTLY_BEFORE_EXPIRY"
|
||||
},
|
||||
"internal_token_rotation": {
|
||||
"secret_class": "GITEA_INTERNAL_TOKEN",
|
||||
"old_secret_fingerprint_algorithm": "SHA256",
|
||||
"old_secret_fingerprint": "42e2af774700405ae32d55c98ca010fdb5da890b08f9e12e77372133c68e0562",
|
||||
"old_secret_reference": "gitea-config://security/INTERNAL_TOKEN",
|
||||
"affected_consumers": [
|
||||
"gitea-config-residue:/data/aios-gitops/runtime/gitea-bin/custom/conf/app.ini.bak-20260706-auth-gitops",
|
||||
"gitea-config:/data/aios-gitops/runtime/gitea-bin/custom/conf/app.ini",
|
||||
"systemd-service:aios-gitea.service"
|
||||
],
|
||||
"replacement_secret_reference": "systemd-credential-encrypted://gitea-internal-token-v2",
|
||||
"rotation_method": "ATOMIC_REPLACE_THEN_REVOKE",
|
||||
"rollback_plan_path": "evidence/governance/bootstrap-rfc3339-fix-20260801/ROLLBACK_PLAN.json",
|
||||
"rollback_plan_sha256": "3283f4c3d6bb7f34ac893789f7e9f5df6c68b7807203e6f471ad92e8e007ff3f",
|
||||
"audit_window_start": "2026-08-01T00:00:00Z",
|
||||
"allowed_operations": [
|
||||
"generate_replacement_internal_token_to_encrypted_runtime_credential",
|
||||
"validate_replacement_internal_token_without_logging_plaintext",
|
||||
"atomically_replace_exact_gitea_internal_token_config_value",
|
||||
"restart_exact_aios_gitea_service",
|
||||
"verify_exact_consumers_healthy",
|
||||
"verify_old_internal_token_authentication_rejected",
|
||||
"remove_or_redact_exact_old_secret_config_residue",
|
||||
"audit_exposure_window",
|
||||
"write_immutable_security_event_receipt"
|
||||
],
|
||||
"access_token_table_exception_applicable": false,
|
||||
"plaintext_secret_in_manifest_allowed": false,
|
||||
"plaintext_secret_in_argv_allowed": false,
|
||||
"plaintext_secret_in_logs_allowed": false,
|
||||
"plaintext_secret_in_git_allowed": false
|
||||
},
|
||||
"phase_b": {
|
||||
"approval_mode": "TWO_STAGE_REQUIRED",
|
||||
"reason": "CURRENT_SCHEMAS_DO_NOT_ATOMICALLY_BIND_BOOTSTRAP_ACTIVATION_AND_FULL_TASK_SCOPED_RELEASE_PAYLOAD",
|
||||
"old_pr_number": 36,
|
||||
"old_head_oid": "a8644fc172bd8ebbba17a16e6bf586c93fd1e793",
|
||||
"old_approval_reusable": false,
|
||||
"successor_required_after_bootstrap_activation": true,
|
||||
"frozen_release_objects_unchanged": true
|
||||
},
|
||||
"frozen_release_objects": {
|
||||
"source_commit_oid": "1857b88690cd1c0119df984534c260b6f217a21a",
|
||||
"source_tree_oid": "17c153de2338f570f70cb7407e7dda3c596b524b",
|
||||
"evidence_closure_oid": "057f5923f72cdb8d002ae40bc0cd584961825801",
|
||||
"evidence_tree_oid": "30be66d153fe9d3758f5fdba89ca4169beab38f8",
|
||||
"git_bundle_sha256": "762e280e240988282bc2c2e65fafecedd596400ab6829016947e84559508a015",
|
||||
"tag_oid": "7f82d77cf7702a6d695b4f0b74da7a9a9ecd7fa3",
|
||||
"peeled_target_oid": "057f5923f72cdb8d002ae40bc0cd584961825801",
|
||||
"release_artifact_sha256": "39523caa48f39b33e5e5df785bb568e0381d8b20301746c280442f73f294f2b2",
|
||||
"rollback_artifact_sha256": "dd0dbbe21db14722996636347ea1d22917436c688d5f0b7055b5bfb1776c4d95",
|
||||
"release_root_sha256": "16e4e881287cef4a02525a6a5b337d3e12995e510832fe04fea2ac196885f3ef",
|
||||
"evidence_manifest_sha256": "3b0b72af400997385e817e9bec677942288f422b69c2d2b52a8a00407fc395e0"
|
||||
},
|
||||
"allowed_status_contexts": [
|
||||
"aios/branch-protection",
|
||||
"aios/evidence-closure",
|
||||
"aios/secret-scan",
|
||||
"aios/state-machine",
|
||||
"aios/validator"
|
||||
],
|
||||
"allowed_identities": ["codex", "gpt"],
|
||||
"allowed_operations": [
|
||||
"install_exact_approved_bootstrap_files",
|
||||
"sign_task_grant_after_owner_exact_head_review",
|
||||
"issue_task_status_credential",
|
||||
"issue_task_merge_credential",
|
||||
"publish_exact_five_statuses",
|
||||
"fast_forward_only_merge",
|
||||
"readback_status_and_ref",
|
||||
"revoke_task_credentials",
|
||||
"remove_credential_projection",
|
||||
"sweep_completed_or_expired_task_credentials"
|
||||
],
|
||||
"operation_limits": {
|
||||
"maximum_status_writes_per_grant": 5,
|
||||
"maximum_merges_per_grant": 1,
|
||||
"maximum_grant_lifetime_seconds": 43200,
|
||||
"automatic_revocation_required": true
|
||||
},
|
||||
"denied_operations": [
|
||||
"branch_push_outside_request_branch",
|
||||
"main_push",
|
||||
"tag_write",
|
||||
"registry_write",
|
||||
"baseline_write",
|
||||
"production_write",
|
||||
"branch_protection_change",
|
||||
"force_push",
|
||||
"squash",
|
||||
"rebase",
|
||||
"merge_commit",
|
||||
"approval_impersonation",
|
||||
"commit_status_database_write",
|
||||
"database_write_outside_exact_task_access_token_revocation",
|
||||
"reuse_pr36_or_pr37_review",
|
||||
"plaintext_secret_persistence"
|
||||
],
|
||||
"runtime_controls": {
|
||||
"global_gitops_writes_enabled": false,
|
||||
"systemd_encrypted_credentials_required": true,
|
||||
"systemd_projection_mode": "0440_ROOT_ROOT_CREDENTIALS_DIRECTORY_ONLY",
|
||||
"systemd_gitea_token_embedded_name": "gitea-token",
|
||||
"sqlite_parent_directory_metadata_write_required": true,
|
||||
"sqlite_sibling_paths_read_only": true,
|
||||
"sqlite_quota_journal_mode": "WAL",
|
||||
"sqlite_quota_synchronous": "FULL",
|
||||
"status_evidence_digest_bound_in_signed_grant": true,
|
||||
"merge_method": "fast-forward-only",
|
||||
"commit_status_database_write_allowed": false
|
||||
},
|
||||
"approved_files": {
|
||||
"deploy/systemd/aios-task-credential-broker@.service": "95f8da02600b3c908013ffa723d79f6fca0c7a4332a077a7b673e97ce35bcd69",
|
||||
"deploy/systemd/aios-task-credential-expiry-sweeper.service": "72a336523983b2486300c4e52f2f01dce392aaf6ffd953041c209d204df85717",
|
||||
"deploy/systemd/aios-task-credential-expiry-sweeper.timer": "49ff9c80cbd0b44fd584dc7d29f4db0d042e33f325b1ed7af81a5be42f1cd011",
|
||||
"deploy/systemd/aios-task-merge-broker@.service": "4e6b8fcf541f225e58ea080107fc30deb0c4ac9eac70217ff9e57081245c106a",
|
||||
"deploy/systemd/aios-task-status-broker@.service": "378a2e8f658cb56cac5dcc151772ec6a00d86f6c1ce3106509b9aa87549dc9da",
|
||||
"evidence/governance/bootstrap-rfc3339-fix-20260801/ROLLBACK_PLAN.json": "3283f4c3d6bb7f34ac893789f7e9f5df6c68b7807203e6f471ad92e8e007ff3f",
|
||||
"evidence/governance/bootstrap-rfc3339-fix-20260801/TEST_SUMMARY.json": "c7d28139fddb9666e60fd62a9a27803dd00a1ebbccbbb75a5121cd8ed3d20fc2",
|
||||
"services/aios-single-owner-governance/aios_single_owner_governance/task_credential_broker.py": "3b2fbbf2698a2d8385a2bf9eccdebbea629642908cbc7a1e8251476475e0d647",
|
||||
"services/aios-single-owner-governance/aios_single_owner_governance/task_credential_expiry_sweeper.py": "8d383768eb0322c84a35282c126bee26a3e6419b125780424ffc7793a504efdf",
|
||||
"services/aios-single-owner-governance/aios_single_owner_governance/task_status_merge_broker.py": "697d8deccdf2fec902dab38a0436075d78a95c1c5b410e115ee74cf79f477e63",
|
||||
"services/aios-single-owner-governance/task-status-merge-grant-v1.schema.json": "34472624261203b5c1a215c141887ea3fed5d68fb3c503c7db2ad9a717862597",
|
||||
"tests/single_owner_governance/test_task_status_merge_broker.py": "af1c5844ee0f7d6907f8cb68d5f503f3bdbd59bb05150e5364eaf7bf2b37be1a"
|
||||
},
|
||||
"aggregate_bindings": {
|
||||
"broker_module_set_sha256": "c73ccaf8f60cf2446a5d4c0b4baf5456cb77961fda682285cb0a4f509ed49caa",
|
||||
"systemd_definition_set_sha256": "3591be96f1717e7a0a57957aafbb233eec2b1617a8fb938889ccd312d812d066",
|
||||
"grant_schema_sha256": "34472624261203b5c1a215c141887ea3fed5d68fb3c503c7db2ad9a717862597",
|
||||
"test_summary_sha256": "c7d28139fddb9666e60fd62a9a27803dd00a1ebbccbbb75a5121cd8ed3d20fc2",
|
||||
"python_executable_sha256": "e1efa562c2cc2e35521a5c9c9b9939921001ff8ca9708a13ef15ace68cc2ccd7"
|
||||
},
|
||||
"machine_gate_requirements": {
|
||||
"compileall": "PASS",
|
||||
"unit_tests": "PASS",
|
||||
"rfc3339_offset_tests": "PASS",
|
||||
"json_schema": "PASS",
|
||||
"systemd_validation": "PASS",
|
||||
"changed_path_secret_scan": "PASS",
|
||||
"fault_injection": "PASS",
|
||||
"scope_boundary_tests": "PASS"
|
||||
}
|
||||
}
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
{
|
||||
"approval_type": "AIOS_EXACT_TWO_PARENT_MERGE_CLOSURE_BOOTSTRAP",
|
||||
"schema_version": 1,
|
||||
"repository": "aios/aios",
|
||||
"expected_governance_base_oid": "9303a5d684e972bd15fe19f61f721075f4dda8b6",
|
||||
"request_branch": "patch/codex/AIOS-V2R1C4-EXACT-TWO-PARENT-MERGE-CLOSURE",
|
||||
"target_ref": "refs/heads/governance/approvals",
|
||||
"issued_at": "2026-08-01T08:48:25Z",
|
||||
"expires_at": "2026-08-01T20:48:25Z",
|
||||
"required_human_approvals": 1,
|
||||
"allowed_human_approvers": [
|
||||
"owner"
|
||||
],
|
||||
"require_latest_head_approval": true,
|
||||
"bootstrap_merge_method": "FAST_FORWARD_ONLY",
|
||||
"exact_head_binding": {
|
||||
"method": "GITEA_EXACT_HEAD_OWNER_REVIEW_AND_PR_METADATA",
|
||||
"head_oid_resolved_after_commit": true,
|
||||
"tree_oid_resolved_after_commit": true,
|
||||
"manifest_blob_oid_resolved_after_commit": true,
|
||||
"manifest_sha256_resolved_after_commit": true,
|
||||
"head_change_invalidates_review": true
|
||||
},
|
||||
"supersedes": {
|
||||
"pr_number": 39,
|
||||
"approved_base_oid": "9303a5d684e972bd15fe19f61f721075f4dda8b6",
|
||||
"approved_head_oid": "52a60a3c419a4a7f70a0af80913532c540ba0a4e",
|
||||
"approved_tree_oid": "37d64c19f06f75589201a8b6277b13356303f04e",
|
||||
"manifest_blob_oid": "f970cd1df80dbd4993cd0eb845608899e53b8315",
|
||||
"manifest_sha256": "d878dc5eaae51f263b5803d36025b37d994602c91d3925430afc1d82acc490cb",
|
||||
"approval_payload_sha256": "b36a25328cc8ab3810bfd967e87391fb128bc0a4ebf3389914084bceb067cd76",
|
||||
"premerge_block_receipt_sha256": "5b9a94cf80f3e16e9f47f90d0c0622f0c001d2e582f4891c51d4c572c1f5fe8b",
|
||||
"marker": "SUPERSEDED_BY_GOVERNANCE_MERGE_CLOSURE_V2",
|
||||
"old_review_reusable": false,
|
||||
"old_pr_immutable": true,
|
||||
"old_pr_merge_allowed": false
|
||||
},
|
||||
"approval_payload_sha256": "13ba236a18e6cf4a7772ce1e90ca520fab34a791bd22f2c6e06f2ee90ffc0817",
|
||||
"approval_payload": {
|
||||
"policy_change_id": "AIOS_EXACT_TWO_PARENT_MERGE_COMMIT_V1",
|
||||
"repository": "aios/aios",
|
||||
"target_ref": "refs/heads/governance/approvals",
|
||||
"merge_mode": "EXACT_TWO_PARENT_COMMIT",
|
||||
"operation_quota": 1,
|
||||
"force": false,
|
||||
"parent_contract": {
|
||||
"parent_count": 2,
|
||||
"parent_0": "expected_old_ref_oid",
|
||||
"parent_1": "approved_pr_head_oid",
|
||||
"tree": "approved_pr_tree_oid",
|
||||
"third_parent_allowed": false
|
||||
},
|
||||
"required_grant_bindings": [
|
||||
"expected_old_ref_oid",
|
||||
"approved_pr_head_oid",
|
||||
"approved_pr_tree_oid",
|
||||
"first_parent_oid",
|
||||
"second_parent_oid",
|
||||
"expected_merge_tree_oid",
|
||||
"expected_merge_commit_oid",
|
||||
"commit_message_utf8",
|
||||
"commit_message_sha256",
|
||||
"author_name",
|
||||
"author_email",
|
||||
"author_epoch",
|
||||
"author_timezone",
|
||||
"committer_name",
|
||||
"committer_email",
|
||||
"committer_epoch",
|
||||
"committer_timezone"
|
||||
],
|
||||
"execution_contract": {
|
||||
"deterministic_oid_recalculation_minimum": 2,
|
||||
"object_only_import": true,
|
||||
"authority_and_gitea_compare_and_swap": true,
|
||||
"update_ref_old_oid_required": true,
|
||||
"statuses_remain_on_approved_pr_head": true,
|
||||
"owner_review_exact_head_required": true,
|
||||
"authority_gitea_api_final_equality_required": true,
|
||||
"git_fsck_connectivity_only_required": true,
|
||||
"journal_mode": "WAL",
|
||||
"journal_synchronous": "FULL",
|
||||
"crash_reconciliation_required": true,
|
||||
"same_request_replay_idempotent": true,
|
||||
"same_idempotency_key_different_request_denied": true
|
||||
},
|
||||
"package_bindings": {
|
||||
"package_revision": "V5R1",
|
||||
"package_zip_sha256_source": "EXTERNAL_SIDECAR",
|
||||
"package_zip_sha256": "e754e6fb9beeda33a17764e8dc515fbd3a2731a95af77e43af9aaacb38a262b6",
|
||||
"package_manifest_sha256": "b5ccbbf7d23191eb09610f30e8062b4febd2344e4525d86b5aeba77d27df0671",
|
||||
"package_manifest_entries": 150,
|
||||
"wheel_sha256": "91c7452965eb668b8f696d83ce43950bfe61805aed29a161610c9ad12d0d7b19",
|
||||
"deployment_definitions_sha256": "b1e7379b1d8cbc5c83b41fee278b2a85e115a6bd071f7e0670563659429b70c0",
|
||||
"manifest_schema_sha256": "31bce5c2960d8c57b26e0fa854196c17e6f018cd547b58679b7877cb602df4f4",
|
||||
"runtime_layout_schema_sha256": "658813b8a0096ab5ee66dd217dc0bbb1b08e09dc33241049f24190bb4df86c9d",
|
||||
"exact_two_parent_broker_sha256": "eb98a9ef64bbbc26e0c0ea9e6f1b7ef247ae685b96ed2b8c0edba7acc39fa4e4",
|
||||
"broker_schema_sha256": "72efc595a85b471260110197ac845171c8447ab4edbf1ded38aed5bee6fe6aba",
|
||||
"validator_schema_sha256": "95c23c5e54b0307fd1ab67dad92edaebdee6d4c495d8009c19015775f75d1f22",
|
||||
"site_config_sha256": "be44861e717513e67b532fdb2f20b67a96aad07e212d521d4a69148ca346760c",
|
||||
"zip_digest_self_reference": false
|
||||
},
|
||||
"frozen_release_objects": {
|
||||
"source_commit_oid": "1857b88690cd1c0119df984534c260b6f217a21a",
|
||||
"source_tree_oid": "17c153de2338f570f70cb7407e7dda3c596b524b",
|
||||
"evidence_closure_oid": "057f5923f72cdb8d002ae40bc0cd584961825801",
|
||||
"evidence_tree_oid": "30be66d153fe9d3758f5fdba89ca4169beab38f8",
|
||||
"git_bundle_sha256": "762e280e240988282bc2c2e65fafecedd596400ab6829016947e84559508a015",
|
||||
"tag_oid": "7f82d77cf7702a6d695b4f0b74da7a9a9ecd7fa3",
|
||||
"peeled_target_oid": "057f5923f72cdb8d002ae40bc0cd584961825801",
|
||||
"release_artifact_sha256": "39523caa48f39b33e5e5df785bb568e0381d8b20301746c280442f73f294f2b2",
|
||||
"rollback_artifact_sha256": "dd0dbbe21db14722996636347ea1d22917436c688d5f0b7055b5bfb1776c4d95",
|
||||
"release_root_sha256": "16e4e881287cef4a02525a6a5b337d3e12995e510832fe04fea2ac196885f3ef",
|
||||
"evidence_manifest_sha256": "3b0b72af400997385e817e9bec677942288f422b69c2d2b52a8a00407fc395e0"
|
||||
},
|
||||
"approved_files": {
|
||||
"deploy/systemd/aios-exact-two-parent-merge-broker@.service": "ca76dd130f0969fcbb5418c4276d4fbbe0671b340ffb2c615098a9989064a8fd",
|
||||
"services/aios-single-owner-governance/aios_single_owner_governance/exact_two_parent_merge_broker.py": "eb98a9ef64bbbc26e0c0ea9e6f1b7ef247ae685b96ed2b8c0edba7acc39fa4e4",
|
||||
"services/aios-single-owner-governance/exact-two-parent-merge-grant-v1.schema.json": "72efc595a85b471260110197ac845171c8447ab4edbf1ded38aed5bee6fe6aba",
|
||||
"services/aios-single-owner-governance/exact-two-parent-merge-closure-v1.schema.json": "95c23c5e54b0307fd1ab67dad92edaebdee6d4c495d8009c19015775f75d1f22",
|
||||
"tests/single_owner_governance/test_exact_two_parent_merge_broker.py": "e3a6ee0d4c0828eeb8113c47f38072746979f1d67da95941cdba6180ce42e800",
|
||||
"tests/single_owner_governance/test_exact_two_parent_merge_closure_schema.py": "a8f479436ab57f1f536f3955fa6f0084c4178a5240f2387078b91ff50eeefd5a",
|
||||
"tests/single_owner_governance/test_exact_two_parent_merge_systemd.py": "358ba693de51f1fd1de11412ef8d981de97ac2ab87510f16eb02884ff6d5fa83",
|
||||
"tests/single_owner_governance/test_exact_two_parent_merge_posix.py": "6299cb774edff9f4f3bf5f7bb1d3bd67c7fb64710924d245052315f20e792fe6",
|
||||
"tests/package_closure/test_v5r1_package_builder.py": "39780b956cadca41dd21f090313b6669534e8d49ef424e4d1baa877e92528566",
|
||||
"tools/build_v5r1_package.py": "74f35d822809ea3d8b6f306f6bdc070fd89f4430c60a2e6a2ddf2cef2bb5ed8f",
|
||||
"evidence/governance/exact-two-parent-merge-closure-20260801/TEST_SUMMARY.json": "908afcef10c80b05183711945ae9bda0f13e61e3a6e2a7c18c537b7fa6967b30",
|
||||
"evidence/governance/exact-two-parent-merge-closure-20260801/ROLLBACK_PLAN.json": "19f81d43245c7980b69847ae43e10a7ed86c16c4d30a06556861ec389702d79e",
|
||||
"evidence/governance/exact-two-parent-merge-closure-20260801/PR39_SUPERSESSION_RECORD.json": "672e7334a574a3ccdafcd2228f4307204ba896995ced82ba03e3d5ce81cf84e9",
|
||||
"v5r1-control-plane/AIOS-V2R1C4-TASK-SCOPED-RELEASE-V5R1/MANIFEST.sha256": "b5ccbbf7d23191eb09610f30e8062b4febd2344e4525d86b5aeba77d27df0671",
|
||||
"v5r1-control-plane/AIOS-V2R1C4-TASK-SCOPED-RELEASE-V5R1/PACKAGE_COMPONENTS.json": "3f558c661cfa1d362deb1aa688f233b5ba2a2f75432a2462cb206da6e8e0ce79",
|
||||
"v5r1-control-plane/AIOS-V2R1C4-TASK-SCOPED-RELEASE-V5R1/PACKAGE_AUDIT.json": "c45efc3d2de7aafa90a8d943c0f9886180cc01e548c00fb39c394a3252b71a20",
|
||||
"v5r1-control-plane/AIOS-V2R1C4-TASK-SCOPED-RELEASE-V5R1/OFFLINE_TEST_REPORT.json": "008a40f85b6f2eb1b033a9df2e20cbdd2141d6371e6a86db0e2743c0da515699",
|
||||
"v5r1-control-plane/AIOS-V2R1C4-TASK-SCOPED-RELEASE-V5R1/PACKAGE_CLOSURE_V5R1.md": "02de7d0a1f7602a196dcb458730ccc84082f6695a9960d4b1758c8a0abe8e2d9"
|
||||
},
|
||||
"machine_gate_requirements": {
|
||||
"compileall": "PASS",
|
||||
"unit_tests": "PASS",
|
||||
"posix_tests": "PASS",
|
||||
"linux_integration": "PASS",
|
||||
"json_schema": "PASS",
|
||||
"systemd_validation": "PASS",
|
||||
"changed_path_secret_scan": "PASS",
|
||||
"git_fsck_connectivity_only": "PASS",
|
||||
"fault_injection": "PASS",
|
||||
"package_digest_closure": "PASS",
|
||||
"scope_boundary_tests": "PASS"
|
||||
}
|
||||
},
|
||||
"allowed_operations": [
|
||||
"install_exact_approved_bootstrap_files_after_owner_review",
|
||||
"publish_exact_five_statuses_to_approved_head",
|
||||
"fast_forward_only_merge_this_bootstrap_pr",
|
||||
"install_exact_two_parent_broker_schema_and_validator",
|
||||
"issue_one_task_scoped_merge_credential",
|
||||
"execute_one_precomputed_two_parent_merge",
|
||||
"compare_and_swap_governance_ref",
|
||||
"revoke_task_credentials",
|
||||
"remove_task_credential_projection"
|
||||
],
|
||||
"denied_operations": [
|
||||
"modify_or_merge_pr39",
|
||||
"delete_pr39_review",
|
||||
"delete_pr39_block_receipt",
|
||||
"main_write",
|
||||
"integration_write",
|
||||
"tag_write",
|
||||
"registry_write",
|
||||
"baseline_write",
|
||||
"production_write",
|
||||
"branch_protection_change",
|
||||
"force_push",
|
||||
"squash",
|
||||
"rebase",
|
||||
"third_parent",
|
||||
"runtime_generated_unapproved_merge_oid",
|
||||
"status_copy_to_merge_commit",
|
||||
"approval_impersonation",
|
||||
"plaintext_secret_persistence"
|
||||
],
|
||||
"runtime_controls": {
|
||||
"global_gitops_writes_enabled": false,
|
||||
"systemd_encrypted_credentials_required": true,
|
||||
"merge_method_for_this_bootstrap_pr": "fast-forward-only",
|
||||
"merge_method_for_successor_phase_b": "exact-two-parent-commit",
|
||||
"automatic_revocation_required": true
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"AIOS_GITOPS_WRITES_ENABLED": false,
|
||||
"acknowledges_pr30_owner_approval_was_missing": true,
|
||||
"activation_prerequisites_artifact_sha256": "729077800c6510bf8930c97a78781218d1bce9ec40157aa99fc2bd205466e895",
|
||||
"activation_prerequisites_commit_oid": "fa038d17b29001e26f1ac88019a407a4b3ebaa7e",
|
||||
"activation_prerequisites_rollback_artifact_sha256": "00e40eace10a0aae1731eb98851fec71a0c13c48e1e6e5a113e30a4356c2086f",
|
||||
"activation_prerequisites_tree_oid": "d5ff67f848807ef2c7a695c05cdd0bb5f2644fc2",
|
||||
"approval_request_branch": "governance/request/single-owner-atomic-activation-owner-ratification-20260729T014918Z-e503d96665ee",
|
||||
"approval_type": "SINGLE_OWNER_GOVERNANCE_ATOMIC_ACTIVATION_OWNER_RATIFICATION",
|
||||
"branch_protection_adapter_sha256": "2706dc6095ff639838f72c91a66c1f80711893c8c0498555b388feaacf2db569",
|
||||
"denied_operations": [
|
||||
"main.write",
|
||||
"integration.write",
|
||||
"tag.write",
|
||||
"registry.write",
|
||||
"baseline.write",
|
||||
"production.deploy",
|
||||
"force_push",
|
||||
"history_rewrite",
|
||||
"governance.direct_push",
|
||||
"approval.impersonate"
|
||||
],
|
||||
"direct_protected_branch_push_allowed": false,
|
||||
"dismiss_stale_approvals": true,
|
||||
"dormant_deployment_evidence_sha256": "c4c326784a3d66d62e32c46be3506c801585c06a34e62c9a1f210492a6c1bc06",
|
||||
"evidence_closure_oid": "5f45139ca5f7f7f3da244141c5115d12f5af58ae",
|
||||
"evidence_closure_sha256": "53e5f2fdc01a6225048dfdf6eac973e99743f0295f17f3a42f01dce7e7050ab8",
|
||||
"expected_governance_base_oid": "f6bef90633f428454e85721f353cd82cdf04caf0",
|
||||
"expires_at": "2026-07-29T13:49:18Z",
|
||||
"explicitly_allowed_operations_after_exact_head_approvals_and_merge": [
|
||||
"approval_validator.activate",
|
||||
"governance_state_machine.activate",
|
||||
"gitea_branch_protection.atomic_update",
|
||||
"rollback_configuration.install",
|
||||
"single_owner_policy.activate",
|
||||
"break_glass.expire",
|
||||
"post_activation_tests.execute"
|
||||
],
|
||||
"force_push_allowed": false,
|
||||
"human_authorizer": "owner",
|
||||
"issued_at": "2026-07-29T01:49:18Z",
|
||||
"legacy_mechanical_approvals": [
|
||||
"codex",
|
||||
"gpt"
|
||||
],
|
||||
"merge_mode": "CREATE_MERGE_COMMIT",
|
||||
"pr30_codex_approval_review_id": 45,
|
||||
"pr30_codex_reviewed_at": "2026-07-29T01:24:38Z",
|
||||
"pr30_codex_reviewed_head_oid": "c2eed5b37983a6461783361efb6de43a28f909bb",
|
||||
"pr30_gpt_approval_review_id": 46,
|
||||
"pr30_gpt_reviewed_at": "2026-07-29T01:25:02Z",
|
||||
"pr30_gpt_reviewed_head_oid": "c2eed5b37983a6461783361efb6de43a28f909bb",
|
||||
"pr30_manifest_path": "approvals/governance-policy/SINGLE-OWNER-GOVERNANCE-ATOMIC-ACTIVATION-20260728T204808Z-4cab1d5829d6.json",
|
||||
"pr30_merge_tree_oid": "219974f085efe473fcc6ad2170f67312d8e57c54",
|
||||
"pr30_merged_at": "2026-07-29T01:25:08Z",
|
||||
"pr30_owner_review_id": 42,
|
||||
"pr30_owner_review_state_observed": "REQUESTED_NOT_APPROVED",
|
||||
"pr30_owner_reviewed_head_oid": null,
|
||||
"pr30_premerge_base_oid": "6e8212608ee310e2fb45f1e802359f4db2e9077b",
|
||||
"production_deployment_allowed": false,
|
||||
"ratifies_pr30_manifest_blob_oid": "38c95dde2d3831f8e3c80b342369f1c3e1cbf34c",
|
||||
"ratifies_pr30_manifest_sha256": "bc19ada0e8eeb47d421a6360cf1d66b6d1129eb6056a447d9ff70ebd5ada1718",
|
||||
"ratifies_pr30_merge_oid": "f6bef90633f428454e85721f353cd82cdf04caf0",
|
||||
"ratifies_pr30_request_oid": "c2eed5b37983a6461783361efb6de43a28f909bb",
|
||||
"ratifies_pr_number": 30,
|
||||
"require_latest_head_approval": true,
|
||||
"required_reviewers": [
|
||||
"owner",
|
||||
"codex",
|
||||
"gpt"
|
||||
],
|
||||
"rollback_configuration_sha256": "eb44fc5f655f8c30c4b687185055ba3a2725189e19538e3a9a46a2c0cf141b8c",
|
||||
"schema": "aios.governance-approval/v1",
|
||||
"state_machine_sha256": "0b08a2c378d143cb9031a25132afc7b7d825bdc03d11efe10610341d4b70167e",
|
||||
"task_id": "AIOS-PR30-SINGLE-OWNER-ATOMIC-ACTIVATION-OWNER-RATIFICATION-20260729",
|
||||
"test_evidence_sha256": "42168da7437028440bece3432d8aa38a8722b4db18244423d9daaa380675c45a",
|
||||
"validator_sha256": "754639846cb15cb997d612a1fe0ceb0224ddef5bbc3ccac9802a3747f122dac1",
|
||||
"wip_blocks_merge": true
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"approval_request_branch": "governance/request/single-owner-governance-activation-prerequisites-20260728T183025127107Z",
|
||||
"approval_type": "SINGLE_OWNER_GOVERNANCE_ACTIVATION_PREREQUISITES",
|
||||
"binds_final_ratification_manifest_blob_oid": "2865fbec766901eb61fc8aed0542970e951e0d1d",
|
||||
"binds_final_ratification_manifest_sha256": "216e54503d8e6a3f4c908084e19ce1e6242d3ba15f7b8011a9a9d9f7975e63e7",
|
||||
"binds_final_ratification_merge_oid": "2bef44f6bc8cf7bc04c959ab629ac95561fa609f",
|
||||
"binds_mirror_receipt_sha256": "6873133f7233bfed0d3b78b01847ad4251b8b67f3b6db7011efd07664f8d088f",
|
||||
"expected_governance_base_oid": "2bef44f6bc8cf7bc04c959ab629ac95561fa609f",
|
||||
"expires_at": "2026-07-29T06:30:25Z",
|
||||
"explicitly_allowed_after_owner_approval_and_legacy_merge": [
|
||||
"task_bound_activation_writer_grant.create",
|
||||
"gitea_owner_review_adapter.create_and_dormant_deploy",
|
||||
"machine_status_producer.create_and_dormant_deploy",
|
||||
"machine_status_producer.credential_rotation_and_revocation",
|
||||
"maintenance_lock.create",
|
||||
"branch_protection_compensation_saga.create",
|
||||
"targeted_online_rollback_executor.create",
|
||||
"disposable_gitea_fixture.create_test_destroy",
|
||||
"single_owner_activation_prerequisite_tests.execute",
|
||||
"activation_prerequisite_evidence.create"
|
||||
],
|
||||
"implementation_branch": "patch/single-owner-governance-activation-prerequisites-20260729",
|
||||
"implementation_paths": [
|
||||
"services/aios-single-owner-governance/**",
|
||||
"tests/single_owner_governance/**",
|
||||
"deploy/systemd/aios-governance-*.service",
|
||||
"deploy/systemd/aios-governance-*.timer"
|
||||
],
|
||||
"issued_at": "2026-07-28T18:30:25Z",
|
||||
"machine_status_contexts": [
|
||||
"aios/machine-gates",
|
||||
"aios/secret-scan",
|
||||
"aios/evidence-closure"
|
||||
],
|
||||
"observed_blockers": {
|
||||
"commit_status_row_count": 0,
|
||||
"disposable_governance_fixture_present": false,
|
||||
"gitea_owner_review_validator_present": false,
|
||||
"gitea_version": "1.22.6",
|
||||
"native_branch_protection_etag_cas_present": false,
|
||||
"required_status_context_producers_present": false,
|
||||
"targeted_online_rollback_executor_present": false
|
||||
},
|
||||
"owner_approval_contract": {
|
||||
"agent_must_not_submit_review": true,
|
||||
"dismissed": false,
|
||||
"head_change_invalidates_approval": true,
|
||||
"only_human_approver": "owner",
|
||||
"owner_user_id": 2,
|
||||
"review_state": "APPROVED",
|
||||
"reviewed_before_expires_at": true,
|
||||
"reviewed_head_oid_must_equal_current_head_oid": true,
|
||||
"stale": false
|
||||
},
|
||||
"permanent_constraints": {
|
||||
"AIOS_GITOPS_WRITES_ENABLED": false,
|
||||
"direct_main_push": false,
|
||||
"force_push": false,
|
||||
"history_rewrite": false,
|
||||
"secret_commit": false,
|
||||
"tag_rewrite": false
|
||||
},
|
||||
"pre_approval_non_authorizations": {
|
||||
"activation_allowed": false,
|
||||
"baseline_write_allowed": false,
|
||||
"branch_protection_change_allowed": false,
|
||||
"fixture_deployment_allowed": false,
|
||||
"integration_write_allowed": false,
|
||||
"main_write_allowed": false,
|
||||
"merge_allowed": false,
|
||||
"production_write_allowed": false,
|
||||
"registry_write_allowed": false,
|
||||
"tag_write_allowed": false,
|
||||
"writer_grant_allowed": false
|
||||
},
|
||||
"reason": "safe-atomic-activation-prerequisites-absent",
|
||||
"repository": "aios/aios",
|
||||
"request_status": "HUMAN_OWNER_APPROVAL_PENDING",
|
||||
"schema": "aios.single-owner-governance-activation-prerequisites/v1",
|
||||
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-PREREQUISITES-20260729",
|
||||
"wip": true,
|
||||
"wip_transition_contract": {
|
||||
"codex_and_gpt_not_requested_before_owner_approval": true,
|
||||
"remove_wip_without_head_change_only_after_owner_approval": true,
|
||||
"then_request_legacy_mechanical_reviews": [
|
||||
"codex",
|
||||
"gpt"
|
||||
],
|
||||
"wip_must_remain_until_owner_exact_head_approval": true
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"AIOS_GITOPS_WRITES_ENABLED": false,
|
||||
"activation_prerequisites_artifact_sha256": "729077800c6510bf8930c97a78781218d1bce9ec40157aa99fc2bd205466e895",
|
||||
"activation_prerequisites_commit_oid": "fa038d17b29001e26f1ac88019a407a4b3ebaa7e",
|
||||
"activation_prerequisites_rollback_artifact_sha256": "00e40eace10a0aae1731eb98851fec71a0c13c48e1e6e5a113e30a4356c2086f",
|
||||
"activation_prerequisites_tree_oid": "d5ff67f848807ef2c7a695c05cdd0bb5f2644fc2",
|
||||
"approval_request_branch": "governance/request/single-owner-governance-atomic-activation-20260728T204808Z-4cab1d5829d6",
|
||||
"approval_type": "SINGLE_OWNER_GOVERNANCE_ATOMIC_ACTIVATION",
|
||||
"branch_protection_adapter_sha256": "2706dc6095ff639838f72c91a66c1f80711893c8c0498555b388feaacf2db569",
|
||||
"denied_operations": [
|
||||
"main.write",
|
||||
"integration.write",
|
||||
"tag.write",
|
||||
"registry.write",
|
||||
"baseline.write",
|
||||
"production.deploy",
|
||||
"force_push",
|
||||
"history_rewrite",
|
||||
"governance.direct_push",
|
||||
"approval.impersonate"
|
||||
],
|
||||
"deployment_mode_before_execution": "DORMANT_SHADOW_ONLY",
|
||||
"direct_protected_branch_push_allowed": false,
|
||||
"dismiss_stale_approvals": true,
|
||||
"dormant_deployment_evidence_sha256": "c4c326784a3d66d62e32c46be3506c801585c06a34e62c9a1f210492a6c1bc06",
|
||||
"evidence_closure_oid": "5f45139ca5f7f7f3da244141c5115d12f5af58ae",
|
||||
"evidence_closure_sha256": "53e5f2fdc01a6225048dfdf6eac973e99743f0295f17f3a42f01dce7e7050ab8",
|
||||
"expected_governance_base_oid": "6e8212608ee310e2fb45f1e802359f4db2e9077b",
|
||||
"expires_at": "2026-07-29T08:48:08Z",
|
||||
"explicitly_allowed_operations_after_exact_head_approvals_and_merge": [
|
||||
"approval_validator.activate",
|
||||
"governance_state_machine.activate",
|
||||
"gitea_branch_protection.atomic_update",
|
||||
"rollback_configuration.install",
|
||||
"single_owner_policy.activate",
|
||||
"break_glass.expire",
|
||||
"post_activation_tests.execute"
|
||||
],
|
||||
"force_push_allowed": false,
|
||||
"issued_at": "2026-07-28T20:48:08Z",
|
||||
"legacy_mechanical_approvals": [
|
||||
"codex",
|
||||
"gpt"
|
||||
],
|
||||
"machine_gates_required": true,
|
||||
"merge_mode": "CREATE_MERGE_COMMIT",
|
||||
"pr26_merge_oid": "a5489f1b67870622162b2daa781bd0237735831a",
|
||||
"pr27_merge_oid": "4e6ef70029b1d94626e432892f7d3beb8e3e5627",
|
||||
"pr28_merge_oid": "2bef44f6bc8cf7bc04c959ab629ac95561fa609f",
|
||||
"pr29_merge_oid": "6e8212608ee310e2fb45f1e802359f4db2e9077b",
|
||||
"production_deployment_allowed": false,
|
||||
"require_latest_head_approval": true,
|
||||
"required_human_authorizer": "owner",
|
||||
"required_reviewers": [
|
||||
"owner",
|
||||
"codex",
|
||||
"gpt"
|
||||
],
|
||||
"rollback_configuration_sha256": "eb44fc5f655f8c30c4b687185055ba3a2725189e19538e3a9a46a2c0cf141b8c",
|
||||
"rollback_required_on_any_activation_failure": true,
|
||||
"schema": "aios.governance-approval/v1",
|
||||
"single_owner_policy_active_before_execution": false,
|
||||
"state_machine_sha256": "0b08a2c378d143cb9031a25132afc7b7d825bdc03d11efe10610341d4b70167e",
|
||||
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-ATOMIC-ACTIVATION-20260729",
|
||||
"test_evidence_sha256": "42168da7437028440bece3432d8aa38a8722b4db18244423d9daaa380675c45a",
|
||||
"validator_sha256": "754639846cb15cb997d612a1fe0ceb0224ddef5bbc3ccac9802a3747f122dac1",
|
||||
"wip_blocks_merge": true
|
||||
}
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
{
|
||||
"acknowledges_pr26_owner_approval_was_missing": true,
|
||||
"approval_request_branch": "governance/request/single-owner-control-plane-deployment-20260728T170740Z-ea7a102dac417c23",
|
||||
"approval_type": "SINGLE_OWNER_GOVERNANCE_CONTROL_PLANE_DEPLOYMENT",
|
||||
"control_plane_targets": {
|
||||
"allowed_human_approvers": [
|
||||
"owner"
|
||||
],
|
||||
"branch_deletion": false,
|
||||
"direct_protected_branch_push": false,
|
||||
"dismiss_stale_approvals": true,
|
||||
"executor_must_not_approve": true,
|
||||
"force_push": false,
|
||||
"governance_model": "SINGLE_OWNER_WITH_MACHINE_GATES_V1",
|
||||
"historical_records_immutable": true,
|
||||
"machine_gates_required": true,
|
||||
"policy_effective_at_set_only_after_activation_tests_pass": true,
|
||||
"protected_branches": [
|
||||
"governance/approvals",
|
||||
"main",
|
||||
"release/**",
|
||||
"integration/**"
|
||||
],
|
||||
"requester_must_not_approve": true,
|
||||
"require_approval_for_latest_commit": true,
|
||||
"required_human_approvals": 1,
|
||||
"required_owner_approvals": 1,
|
||||
"required_status_contexts": [
|
||||
"aios/machine-gates",
|
||||
"aios/secret-scan",
|
||||
"aios/evidence-closure"
|
||||
],
|
||||
"tag_rewrite": false,
|
||||
"wip_blocks_merge": true
|
||||
},
|
||||
"does_not_claim_pr26_met_old_two_natural_person_policy": true,
|
||||
"expected_authoritative_governance_base_oid": "ac2dc5c30eb35748ef11c98297812729d0fd5828",
|
||||
"expected_gitea_governance_base_oid": "a5489f1b67870622162b2daa781bd0237735831a",
|
||||
"expires_at": "2026-07-29T05:07:40Z",
|
||||
"explicitly_allowed_operations_after_exact_head_approvals_and_merge": [
|
||||
"approval_mirror.fast_forward_governance_only",
|
||||
"approval_validator.update",
|
||||
"state_machine.update",
|
||||
"gitea_branch_protection.update_via_authenticated_api_compare_and_swap",
|
||||
"governance_policy.activate",
|
||||
"rollback_configuration.install",
|
||||
"governance_acceptance_tests.execute",
|
||||
"single_owner_governance_activation_receipt.create"
|
||||
],
|
||||
"explicitly_denied_operations": [
|
||||
"force_push",
|
||||
"plus_refspec",
|
||||
"tag_fetch",
|
||||
"prune",
|
||||
"direct_git_database_write",
|
||||
"direct_protected_ref_push",
|
||||
"main_write",
|
||||
"integration_write",
|
||||
"tag_write",
|
||||
"registry_write",
|
||||
"baseline_write",
|
||||
"production_write",
|
||||
"history_rewrite",
|
||||
"branch_delete",
|
||||
"secret_read",
|
||||
"approval_impersonation"
|
||||
],
|
||||
"global_constraints": {
|
||||
"AIOS_GITOPS_WRITES_ENABLED": false,
|
||||
"direct_main_push": false,
|
||||
"force_push_used": false,
|
||||
"owner_approval_respected": true,
|
||||
"secrets_committed": false,
|
||||
"tag_rewritten": false
|
||||
},
|
||||
"implementation_surface": {
|
||||
"approval_validator": [
|
||||
"services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/approval.py",
|
||||
"services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/models.py",
|
||||
"services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/policy.py"
|
||||
],
|
||||
"runtime": [
|
||||
"active-runtime/app/silent_release_controller.py",
|
||||
"active-runtime/app/silent_release_hardening.py"
|
||||
],
|
||||
"state_machine": [
|
||||
"services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/store.py",
|
||||
"services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/workflow.py",
|
||||
"services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/release.py"
|
||||
],
|
||||
"systemd": [
|
||||
"deploy/systemd/aios-autonomous-release-candidate.service",
|
||||
"deploy/systemd/aios-silent-release-controller.service"
|
||||
]
|
||||
},
|
||||
"issued_at": "2026-07-28T17:07:40Z",
|
||||
"legacy_gitea_protection_gate": {
|
||||
"account_reviews_do_not_prove_distinct_natural_persons": true,
|
||||
"both_must_approve_this_exact_head": true,
|
||||
"current_approval_whitelist": [
|
||||
"codex",
|
||||
"gpt"
|
||||
],
|
||||
"current_required_approvals": 2
|
||||
},
|
||||
"migration_mode": "OWNER_BREAK_GLASS_GOVERNANCE_RESET_RATIFICATION",
|
||||
"mirror_contract": {
|
||||
"expected_chain_contains_policy_merge_oid": "a5489f1b67870622162b2daa781bd0237735831a",
|
||||
"expected_pre_sync_authoritative_oid": "ac2dc5c30eb35748ef11c98297812729d0fd5828",
|
||||
"fast_forward_only": true,
|
||||
"force": false,
|
||||
"fsck_connectivity_only_required": true,
|
||||
"only_ref": "refs/heads/governance/approvals",
|
||||
"plus_refspec": false,
|
||||
"prune": false,
|
||||
"quarantine_required": true,
|
||||
"source": "gitea_repository_file_url_only",
|
||||
"tags": false
|
||||
},
|
||||
"owner_break_glass_ratification": {
|
||||
"attests_reason": "single-maintainer-system",
|
||||
"attests_scope": "governance-policy-migration-only",
|
||||
"created_before_expires_at": true,
|
||||
"dismissed": false,
|
||||
"required": true,
|
||||
"review_state": "APPROVED",
|
||||
"reviewed_head_oid_must_equal_current_pr_head_oid": true,
|
||||
"reviewer_user_id": 2,
|
||||
"reviewer_username": "owner",
|
||||
"stale": false
|
||||
},
|
||||
"pre_approval_non_authorizations": {
|
||||
"approval_validator_update_allowed": false,
|
||||
"development_allowed": false,
|
||||
"gitea_branch_protection_update_allowed": false,
|
||||
"governance_policy_activation_allowed": false,
|
||||
"merge_allowed": false,
|
||||
"mirror_allowed": false,
|
||||
"production_change_allowed": false,
|
||||
"rollback_install_allowed": false,
|
||||
"standing_writer_policy_allowed": false,
|
||||
"state_machine_update_allowed": false
|
||||
},
|
||||
"pre_cutover_protection_snapshot": {
|
||||
"protected_branch": [
|
||||
{
|
||||
"approvals_whitelist_team_i_ds": "null",
|
||||
"approvals_whitelist_user_i_ds": "[42,43]",
|
||||
"block_on_official_review_requests": 1,
|
||||
"block_on_outdated_branch": 1,
|
||||
"block_on_rejected_reviews": 1,
|
||||
"branch_name": "governance/approvals",
|
||||
"can_push": 0,
|
||||
"created_unix": 1785093201,
|
||||
"dismiss_stale_approvals": 1,
|
||||
"enable_approvals_whitelist": 1,
|
||||
"enable_merge_whitelist": 1,
|
||||
"enable_status_check": 0,
|
||||
"enable_whitelist": 0,
|
||||
"id": 1,
|
||||
"ignore_stale_approvals": 0,
|
||||
"merge_whitelist_team_i_ds": "null",
|
||||
"merge_whitelist_user_i_ds": "[42,43]",
|
||||
"protected_file_patterns": "",
|
||||
"repo_id": 1,
|
||||
"require_signed_commits": 0,
|
||||
"required_approvals": 2,
|
||||
"status_check_contexts": "[]",
|
||||
"unprotected_file_patterns": "",
|
||||
"updated_unix": 1785093422,
|
||||
"whitelist_deploy_keys": 0,
|
||||
"whitelist_team_i_ds": "null",
|
||||
"whitelist_user_i_ds": "null"
|
||||
},
|
||||
{
|
||||
"approvals_whitelist_team_i_ds": "null",
|
||||
"approvals_whitelist_user_i_ds": "[42,43]",
|
||||
"block_on_official_review_requests": 1,
|
||||
"block_on_outdated_branch": 1,
|
||||
"block_on_rejected_reviews": 1,
|
||||
"branch_name": "integration/chatgpt-autonomous-completion-20260726",
|
||||
"can_push": 0,
|
||||
"created_unix": 1785093201,
|
||||
"dismiss_stale_approvals": 1,
|
||||
"enable_approvals_whitelist": 1,
|
||||
"enable_merge_whitelist": 1,
|
||||
"enable_status_check": 0,
|
||||
"enable_whitelist": 0,
|
||||
"id": 3,
|
||||
"ignore_stale_approvals": 0,
|
||||
"merge_whitelist_team_i_ds": "null",
|
||||
"merge_whitelist_user_i_ds": "[42,43]",
|
||||
"protected_file_patterns": "",
|
||||
"repo_id": 1,
|
||||
"require_signed_commits": 0,
|
||||
"required_approvals": 2,
|
||||
"status_check_contexts": "[]",
|
||||
"unprotected_file_patterns": "",
|
||||
"updated_unix": 1785093422,
|
||||
"whitelist_deploy_keys": 0,
|
||||
"whitelist_team_i_ds": "null",
|
||||
"whitelist_user_i_ds": "null"
|
||||
},
|
||||
{
|
||||
"approvals_whitelist_team_i_ds": "null",
|
||||
"approvals_whitelist_user_i_ds": "[42,43]",
|
||||
"block_on_official_review_requests": 1,
|
||||
"block_on_outdated_branch": 1,
|
||||
"block_on_rejected_reviews": 1,
|
||||
"branch_name": "main",
|
||||
"can_push": 0,
|
||||
"created_unix": 1785093201,
|
||||
"dismiss_stale_approvals": 1,
|
||||
"enable_approvals_whitelist": 1,
|
||||
"enable_merge_whitelist": 1,
|
||||
"enable_status_check": 0,
|
||||
"enable_whitelist": 0,
|
||||
"id": 2,
|
||||
"ignore_stale_approvals": 0,
|
||||
"merge_whitelist_team_i_ds": "null",
|
||||
"merge_whitelist_user_i_ds": "[42,43]",
|
||||
"protected_file_patterns": "",
|
||||
"repo_id": 1,
|
||||
"require_signed_commits": 0,
|
||||
"required_approvals": 2,
|
||||
"status_check_contexts": "[]",
|
||||
"unprotected_file_patterns": "",
|
||||
"updated_unix": 1785093422,
|
||||
"whitelist_deploy_keys": 0,
|
||||
"whitelist_team_i_ds": "null",
|
||||
"whitelist_user_i_ds": "null"
|
||||
}
|
||||
],
|
||||
"protected_tag": []
|
||||
},
|
||||
"pre_cutover_protection_snapshot_sha256": "2bc353f34689242cabebf89856d3faab0e1c496b5d33761dfb7cbd47904516f7",
|
||||
"ratifies_policy_manifest_blob_oid": "65f44007b10fc2a912d69e3f9081c0cfff77dfad",
|
||||
"ratifies_policy_manifest_path": "approvals/governance-policy/SINGLE-OWNER-GOVERNANCE-MIGRATION-20260728T162246Z.json",
|
||||
"ratifies_policy_manifest_sha256": "c097475afbe3074e6482f125b635981f8c0284f3d763c926e2a63e1a3ee1c6c3",
|
||||
"ratifies_policy_merge_oid": "a5489f1b67870622162b2daa781bd0237735831a",
|
||||
"ratifies_policy_pr_number": 26,
|
||||
"ratifies_policy_request_oid": "312d3b483049a748675e887a516ec739c81ff474",
|
||||
"ratifies_policy_tree_oid": "5c6974c058bba6210dcc1bdd0968fb7e6c84a844",
|
||||
"repository": "aios/aios",
|
||||
"request_status": "HUMAN_OWNER_AND_LEGACY_PROTECTION_APPROVAL_PENDING",
|
||||
"required_machine_gates": [
|
||||
"owner_exact_head_review_valid",
|
||||
"legacy_protection_reviews_valid",
|
||||
"ordinary_merge_commit",
|
||||
"parent_order_verified",
|
||||
"manifest_add_only",
|
||||
"quarantine_verification",
|
||||
"approval_mirror_fast_forward",
|
||||
"validator_tests",
|
||||
"state_machine_tests",
|
||||
"protection_compare_and_swap",
|
||||
"positive_governance_tests",
|
||||
"negative_governance_tests",
|
||||
"secret_scan",
|
||||
"rollback_integrity",
|
||||
"production_write_false"
|
||||
],
|
||||
"rollback_configuration_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-MIGRATION-20260729/rollback-configuration.tar",
|
||||
"rollback_configuration_sha256": "fc8dcf68aaf53edb0cda45833ee45ba5b8b205791af9073258ca617c8b8187d6",
|
||||
"schema": "aios.single-owner-governance-control-plane-deployment/v1",
|
||||
"scope": "pr26-ratification-mirror-and-single-owner-control-plane-deployment-only",
|
||||
"task_id": "AIOS-SINGLE-OWNER-CONTROL-PLANE-DEPLOYMENT-20260729"
|
||||
}
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"acknowledges_pr26_and_pr27_owner_approval_was_missing": true,
|
||||
"approval_request_branch": "governance/request/single-owner-governance-final-owner-ratification-20260728T173634412838Z",
|
||||
"approval_type": "SINGLE_OWNER_GOVERNANCE_FINAL_OWNER_RATIFICATION",
|
||||
"backup_checksums_sha256": "72e2fe0b03d1011c872bb39a6cff489f08398ac7d52092f720cbbc63bcf6f753",
|
||||
"backup_root": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-MIGRATION-20260729/pre-migration-backup",
|
||||
"break_glass_expires_immediately_after_activation": true,
|
||||
"does_not_claim_old_two_natural_person_compliance": true,
|
||||
"expires_at": "2026-07-29T05:36:34Z",
|
||||
"inherits_only_pr27_control_plane_allowlist": [
|
||||
"approval_mirror.fast_forward_governance_only",
|
||||
"approval_validator.update",
|
||||
"state_machine.update",
|
||||
"gitea_branch_protection.update_via_authenticated_api_compare_and_swap",
|
||||
"governance_policy.activate",
|
||||
"rollback_configuration.install",
|
||||
"governance_acceptance_tests.execute",
|
||||
"single_owner_governance_activation_receipt.create"
|
||||
],
|
||||
"issued_at": "2026-07-28T17:36:34Z",
|
||||
"legacy_mechanical_protection_gate": {
|
||||
"account_reviews_do_not_count_as_owner_ratification": true,
|
||||
"account_reviews_do_not_prove_distinct_natural_persons": true,
|
||||
"approval_and_merge_whitelist_user_ids": [
|
||||
42,
|
||||
43
|
||||
],
|
||||
"both_reviews_must_bind_current_request_head": true,
|
||||
"required_approvals": 2,
|
||||
"reviewers": [
|
||||
"codex",
|
||||
"gpt"
|
||||
]
|
||||
},
|
||||
"merge_contract": {
|
||||
"first_parent_must_equal_premerge_governance_oid": true,
|
||||
"force": false,
|
||||
"merge_mode": "CREATE_MERGE_COMMIT",
|
||||
"parent_count": 2,
|
||||
"rebase": false,
|
||||
"second_parent_must_equal_final_ratification_request_oid": true,
|
||||
"single_file_add_only": true,
|
||||
"squash": false
|
||||
},
|
||||
"migration_mode": "OWNER_BREAK_GLASS_GOVERNANCE_RESET",
|
||||
"mirror_contract": {
|
||||
"expected_authoritative_pre_oid": "ac2dc5c30eb35748ef11c98297812729d0fd5828",
|
||||
"fast_forward_only": true,
|
||||
"force": false,
|
||||
"fsck_connectivity_only_required": true,
|
||||
"only_ref": "refs/heads/governance/approvals",
|
||||
"plus_refspec": false,
|
||||
"prune": false,
|
||||
"quarantine_required": true,
|
||||
"required_chain": [
|
||||
"ac2dc5c30eb35748ef11c98297812729d0fd5828",
|
||||
"a5489f1b67870622162b2daa781bd0237735831a",
|
||||
"4e6ef70029b1d94626e432892f7d3beb8e3e5627",
|
||||
"FINAL_RATIFICATION_MERGE_OID"
|
||||
],
|
||||
"source": "gitea_repository_file_url_only",
|
||||
"tags": false
|
||||
},
|
||||
"owner_ratification_contract": {
|
||||
"agent_must_not_submit_review": true,
|
||||
"dismissed": false,
|
||||
"only_valid_human_approver": "owner",
|
||||
"owner_user_id": 2,
|
||||
"review_state": "APPROVED",
|
||||
"review_type_must_be_approved_not_requested": true,
|
||||
"reviewed_before_expires_at": true,
|
||||
"reviewed_before_merge": true,
|
||||
"reviewed_head_oid_must_equal_current_request_head_oid": true,
|
||||
"stale": false
|
||||
},
|
||||
"permanent_constraints": {
|
||||
"AIOS_GITOPS_WRITES_ENABLED": false,
|
||||
"direct_main_push": false,
|
||||
"force_push_used": false,
|
||||
"owner_approval_respected": true,
|
||||
"secrets_committed": false,
|
||||
"tag_rewritten": false
|
||||
},
|
||||
"post_merge_activation_preconditions": [
|
||||
"owner_exact_head_approval_valid",
|
||||
"legacy_mechanical_reviews_valid",
|
||||
"ordinary_merge_commit",
|
||||
"one_file_add_only",
|
||||
"quarantine_chain_verified",
|
||||
"approval_mirror_fast_forward_closed",
|
||||
"rollback_integrity_pass",
|
||||
"validator_and_state_machine_tests_pass",
|
||||
"protection_compare_and_swap_pass",
|
||||
"positive_and_negative_governance_tests_pass",
|
||||
"production_write_false"
|
||||
],
|
||||
"pre_activation_protection_snapshot": {
|
||||
"protected_branch": [
|
||||
{
|
||||
"approvals_whitelist_team_i_ds": "null",
|
||||
"approvals_whitelist_user_i_ds": "[42,43]",
|
||||
"block_on_official_review_requests": 1,
|
||||
"block_on_outdated_branch": 1,
|
||||
"block_on_rejected_reviews": 1,
|
||||
"branch_name": "governance/approvals",
|
||||
"can_push": 0,
|
||||
"created_unix": 1785093201,
|
||||
"dismiss_stale_approvals": 1,
|
||||
"enable_approvals_whitelist": 1,
|
||||
"enable_merge_whitelist": 1,
|
||||
"enable_status_check": 0,
|
||||
"enable_whitelist": 0,
|
||||
"id": 1,
|
||||
"ignore_stale_approvals": 0,
|
||||
"merge_whitelist_team_i_ds": "null",
|
||||
"merge_whitelist_user_i_ds": "[42,43]",
|
||||
"protected_file_patterns": "",
|
||||
"repo_id": 1,
|
||||
"require_signed_commits": 0,
|
||||
"required_approvals": 2,
|
||||
"status_check_contexts": "[]",
|
||||
"unprotected_file_patterns": "",
|
||||
"updated_unix": 1785093422,
|
||||
"whitelist_deploy_keys": 0,
|
||||
"whitelist_team_i_ds": "null",
|
||||
"whitelist_user_i_ds": "null"
|
||||
},
|
||||
{
|
||||
"approvals_whitelist_team_i_ds": "null",
|
||||
"approvals_whitelist_user_i_ds": "[42,43]",
|
||||
"block_on_official_review_requests": 1,
|
||||
"block_on_outdated_branch": 1,
|
||||
"block_on_rejected_reviews": 1,
|
||||
"branch_name": "integration/chatgpt-autonomous-completion-20260726",
|
||||
"can_push": 0,
|
||||
"created_unix": 1785093201,
|
||||
"dismiss_stale_approvals": 1,
|
||||
"enable_approvals_whitelist": 1,
|
||||
"enable_merge_whitelist": 1,
|
||||
"enable_status_check": 0,
|
||||
"enable_whitelist": 0,
|
||||
"id": 3,
|
||||
"ignore_stale_approvals": 0,
|
||||
"merge_whitelist_team_i_ds": "null",
|
||||
"merge_whitelist_user_i_ds": "[42,43]",
|
||||
"protected_file_patterns": "",
|
||||
"repo_id": 1,
|
||||
"require_signed_commits": 0,
|
||||
"required_approvals": 2,
|
||||
"status_check_contexts": "[]",
|
||||
"unprotected_file_patterns": "",
|
||||
"updated_unix": 1785093422,
|
||||
"whitelist_deploy_keys": 0,
|
||||
"whitelist_team_i_ds": "null",
|
||||
"whitelist_user_i_ds": "null"
|
||||
},
|
||||
{
|
||||
"approvals_whitelist_team_i_ds": "null",
|
||||
"approvals_whitelist_user_i_ds": "[42,43]",
|
||||
"block_on_official_review_requests": 1,
|
||||
"block_on_outdated_branch": 1,
|
||||
"block_on_rejected_reviews": 1,
|
||||
"branch_name": "main",
|
||||
"can_push": 0,
|
||||
"created_unix": 1785093201,
|
||||
"dismiss_stale_approvals": 1,
|
||||
"enable_approvals_whitelist": 1,
|
||||
"enable_merge_whitelist": 1,
|
||||
"enable_status_check": 0,
|
||||
"enable_whitelist": 0,
|
||||
"id": 2,
|
||||
"ignore_stale_approvals": 0,
|
||||
"merge_whitelist_team_i_ds": "null",
|
||||
"merge_whitelist_user_i_ds": "[42,43]",
|
||||
"protected_file_patterns": "",
|
||||
"repo_id": 1,
|
||||
"require_signed_commits": 0,
|
||||
"required_approvals": 2,
|
||||
"status_check_contexts": "[]",
|
||||
"unprotected_file_patterns": "",
|
||||
"updated_unix": 1785093422,
|
||||
"whitelist_deploy_keys": 0,
|
||||
"whitelist_team_i_ds": "null",
|
||||
"whitelist_user_i_ds": "null"
|
||||
}
|
||||
],
|
||||
"protected_tag": []
|
||||
},
|
||||
"pre_activation_protection_snapshot_sha256": "2bc353f34689242cabebf89856d3faab0e1c496b5d33761dfb7cbd47904516f7",
|
||||
"pre_approval_non_authorizations": {
|
||||
"baseline_write_allowed": false,
|
||||
"branch_protection_update_allowed": false,
|
||||
"control_plane_activation_allowed": false,
|
||||
"development_allowed": false,
|
||||
"integration_write_allowed": false,
|
||||
"main_write_allowed": false,
|
||||
"merge_allowed": false,
|
||||
"mirror_allowed": false,
|
||||
"production_write_allowed": false,
|
||||
"registry_write_allowed": false,
|
||||
"rollback_install_allowed": false,
|
||||
"standing_writer_allowed": false,
|
||||
"state_machine_update_allowed": false,
|
||||
"tag_write_allowed": false,
|
||||
"validator_update_allowed": false
|
||||
},
|
||||
"ratification_scope": "GOVERNANCE_MIGRATION_AND_CONTROL_PLANE_ACTIVATION_ONLY",
|
||||
"ratifies_pr26_manifest_blob_oid": "65f44007b10fc2a912d69e3f9081c0cfff77dfad",
|
||||
"ratifies_pr26_manifest_path": "approvals/governance-policy/SINGLE-OWNER-GOVERNANCE-MIGRATION-20260728T162246Z.json",
|
||||
"ratifies_pr26_manifest_sha256": "c097475afbe3074e6482f125b635981f8c0284f3d763c926e2a63e1a3ee1c6c3",
|
||||
"ratifies_pr26_merge_oid": "a5489f1b67870622162b2daa781bd0237735831a",
|
||||
"ratifies_pr26_request_oid": "312d3b483049a748675e887a516ec739c81ff474",
|
||||
"ratifies_pr26_tree_oid": "5c6974c058bba6210dcc1bdd0968fb7e6c84a844",
|
||||
"ratifies_pr27_manifest_blob_oid": "77f3c2ab85cba2c30c8c0aa36e591232d0f4c396",
|
||||
"ratifies_pr27_manifest_path": "approvals/governance-policy/SINGLE-OWNER-GOVERNANCE-CONTROL-PLANE-DEPLOYMENT-20260728T170740Z-ea7a102dac417c23.json",
|
||||
"ratifies_pr27_manifest_sha256": "2ed09180c0aeadbef2e5d6310efe7c2d0de15e0e87b6135e7a8494cb4f0967c7",
|
||||
"ratifies_pr27_merge_oid": "4e6ef70029b1d94626e432892f7d3beb8e3e5627",
|
||||
"ratifies_pr27_request_oid": "80feef309fd8e1db7795f68d5bf0f15ce805b365",
|
||||
"ratifies_pr27_tree_oid": "85af0d1e88e8484a9c3a0562a81cbc1de09250ae",
|
||||
"ratifies_pr_numbers": [
|
||||
26,
|
||||
27
|
||||
],
|
||||
"repository": "aios/aios",
|
||||
"request_status": "HUMAN_OWNER_APPROVAL_PENDING",
|
||||
"retroactive_authorization_for_other_operations": false,
|
||||
"rollback_archive_member_hashes": {
|
||||
"pre-migration-backup/app.ini": "0693e4fa8296a22533429339faeee2ac1856b6c966c6c1009bb139ab4784f687",
|
||||
"pre-migration-backup/authoritative-aios.bundle": "4fb4ccc979c3061065cde451af0e733326cb56fa6f860124bb73f153a60907c9",
|
||||
"pre-migration-backup/authoritative-refs.txt": "85cb664c8edf3902bebfc6c2253a3ee16cff8534f86561ba771a73df160186c6",
|
||||
"pre-migration-backup/backup-metadata.json": "a38ef6dacb52ae61c4332bf8d087f479fb0d5f51d5f636a132a711bddd6e7732",
|
||||
"pre-migration-backup/checksums.sha256": "72e2fe0b03d1011c872bb39a6cff489f08398ac7d52092f720cbbc63bcf6f753",
|
||||
"pre-migration-backup/gitea-aios.bundle": "a343c7437f3a1a2aa842dd9d986a1d3cf80691058faae978565224320ff5ed90",
|
||||
"pre-migration-backup/gitea-refs.txt": "1792b2c4f1cf1f232704601ee968e16425f926a6eaab1bdcddf273525d046e4d",
|
||||
"pre-migration-backup/gitea.db": "aadcc1587a05827ed3b2c4ffc2403f26139020cd0328afaa03f7afbf6083a2f1",
|
||||
"pre-migration-backup/governance-identities.json": "2d7b4e72d1fdf187425092089589547e0d76784c611d00f915a8c212f17d3b15",
|
||||
"pre-migration-backup/protected-branch.json": "5cc4ae4fe5614baa3d4bb83d71872a4ad1ce3c5e39061e02b336be40d5b5b75d",
|
||||
"pre-migration-backup/protected-tag.json": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"pre-migration-backup/repository/active-runtime/app/aios_isolated_writer_policy.py": "d86ec23fd5d8b7e9844c0895c50385b6f8f3071c3dac3c0f26c70768ae00a188",
|
||||
"pre-migration-backup/repository/active-runtime/app/isolated_writer_authz.py": "cfae1cdf70c1dabbe013e9365c308a45f11acf20035881a3bb6143774682bbf0",
|
||||
"pre-migration-backup/repository/active-runtime/app/isolated_writer_path_policy.py": "c6c9ea8e45dfc03c4703503f4dc9c92f887bbacdbf1c7779a487567eee6c257c",
|
||||
"pre-migration-backup/repository/active-runtime/app/silent_release_controller.py": "524a0a9961b15c2a35b669d1b89b2142377c13ddd3c721780f374331b7d2b6eb",
|
||||
"pre-migration-backup/repository/active-runtime/app/silent_release_hardening.py": "524aba7034f2cd89d897fe01e9799c835bae9f11ca738182ce469aa59e05cc1a",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/approval.py": "cc937a81aef881206cb1d6fd2ad1feb1a1c2e0ad91c908095d56fd96402c3373",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/models.py": "aa3b3dd41c03c17e4e116ce444d9f4f779694a9cde684e8329cf892bd319069a",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/policy.py": "24b3f3326e6dcd1a4c376d892c53ad20c68e0dd6f861a3a34e11c9a2bba4c756",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/release.py": "db535e5006f8e4a3ab80c2f689213045c120b01cda9b43ef461a3022096c9d19",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/store.py": "31e1f3b904b0bfc8822d21bfd166a45388f122398d1ec76b803e90ee06dc92f0",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/workflow.py": "5f6a8e3a4d66f47717e95842f2eaa443e3cfa321cb08026cc82d74c4ad353bcf",
|
||||
"pre-migration-backup/repository/tests/autonomous_release/test_core_contracts.py": "71852070db50a04901c141213cadc987ae17ec678fe27a901fee165b6d1c3e2a",
|
||||
"pre-migration-backup/repository/tests/autonomous_release/test_external_gates.py": "91cca37eb1d40f0cf9fc38b12e2123045ffebd794a34d6cdef3f1dec470fffd2",
|
||||
"pre-migration-backup/repository/tests/autonomous_release/test_release_gates.py": "d82b23ac00f5389f1a0de628bda9dff898d33f80d375cf6ff74f1531fd138134",
|
||||
"pre-migration-backup/repository/tests/autonomous_release/test_state_and_workflow.py": "c0395a901732adb0b2f1402e6a1cf5202d54aa597bcaec9584a7ea7589c53917"
|
||||
},
|
||||
"rollback_configuration_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-MIGRATION-20260729/rollback-configuration.tar",
|
||||
"rollback_configuration_sha256": "fc8dcf68aaf53edb0cda45833ee45ba5b8b205791af9073258ca617c8b8187d6",
|
||||
"schema": "aios.single-owner-governance-final-owner-ratification/v1",
|
||||
"single_owner_policy_effective_at": "FINAL_RATIFICATION_MERGE_OID",
|
||||
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-FINAL-RATIFICATION-20260729"
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
{
|
||||
"allowed_human_approvers": [
|
||||
"owner"
|
||||
],
|
||||
"approval_expires_at": "2026-07-29T16:22:46Z",
|
||||
"approval_request_branch": "governance/request/single-owner-governance-migration-20260728T162246Z",
|
||||
"approval_type": "SINGLE_OWNER_GOVERNANCE_POLICY",
|
||||
"approval_types_migrated_after_policy_effective_at": [
|
||||
"SOURCE_MAPPING_AND_IMPORT_EXECUTION_RENEWAL",
|
||||
"CONTROL_PLANE_DEPLOYMENT_APPROVAL",
|
||||
"DEVELOPMENT_MERGE_APPROVAL",
|
||||
"RELEASE_CANDIDATE_APPROVAL",
|
||||
"PRODUCTION_RELEASE_APPROVAL",
|
||||
"TAG_APPROVAL",
|
||||
"REGISTRY_BASELINE_PROMOTION_APPROVAL",
|
||||
"PRODUCTION_DEPLOYMENT_APPROVAL",
|
||||
"ROLLBACK_APPROVAL"
|
||||
],
|
||||
"atomic_migration": {
|
||||
"backup_checksums_sha256": "72e2fe0b03d1011c872bb39a6cff489f08398ac7d52092f720cbbc63bcf6f753",
|
||||
"backup_root": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-MIGRATION-20260729/pre-migration-backup",
|
||||
"direct_database_update": false,
|
||||
"direct_ref_update": false,
|
||||
"dual_capable_validator_shadow_required": true,
|
||||
"identity_snapshot_sha256": "2d7b4e72d1fdf187425092089589547e0d76784c611d00f915a8c212f17d3b15",
|
||||
"live_protection_canonical_sha256": "2bc353f34689242cabebf89856d3faab0e1c496b5d33761dfb7cbd47904516f7",
|
||||
"maintenance_lock_required": true,
|
||||
"old_rules_remain_active_until_owner_approval_and_machine_gates_pass": true,
|
||||
"protected_branch_snapshot_sha256": "2bc353f34689242cabebf89856d3faab0e1c496b5d33761dfb7cbd47904516f7",
|
||||
"protection_update_method": "authenticated_admin_API_compare_and_swap",
|
||||
"reviewable_backup_file_hashes": {
|
||||
"app.ini": "0693e4fa8296a22533429339faeee2ac1856b6c966c6c1009bb139ab4784f687",
|
||||
"authoritative-aios.bundle": "4fb4ccc979c3061065cde451af0e733326cb56fa6f860124bb73f153a60907c9",
|
||||
"authoritative-refs.txt": "85cb664c8edf3902bebfc6c2253a3ee16cff8534f86561ba771a73df160186c6",
|
||||
"backup-metadata.json": "a38ef6dacb52ae61c4332bf8d087f479fb0d5f51d5f636a132a711bddd6e7732",
|
||||
"checksums.sha256": "72e2fe0b03d1011c872bb39a6cff489f08398ac7d52092f720cbbc63bcf6f753",
|
||||
"gitea-aios.bundle": "a343c7437f3a1a2aa842dd9d986a1d3cf80691058faae978565224320ff5ed90",
|
||||
"gitea-refs.txt": "1792b2c4f1cf1f232704601ee968e16425f926a6eaab1bdcddf273525d046e4d",
|
||||
"gitea.db": "aadcc1587a05827ed3b2c4ffc2403f26139020cd0328afaa03f7afbf6083a2f1",
|
||||
"governance-identities.json": "2d7b4e72d1fdf187425092089589547e0d76784c611d00f915a8c212f17d3b15",
|
||||
"protected-branch.json": "5cc4ae4fe5614baa3d4bb83d71872a4ad1ce3c5e39061e02b336be40d5b5b75d",
|
||||
"protected-tag.json": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"repository/active-runtime/app/aios_isolated_writer_policy.py": "d86ec23fd5d8b7e9844c0895c50385b6f8f3071c3dac3c0f26c70768ae00a188",
|
||||
"repository/active-runtime/app/isolated_writer_authz.py": "cfae1cdf70c1dabbe013e9365c308a45f11acf20035881a3bb6143774682bbf0",
|
||||
"repository/active-runtime/app/isolated_writer_path_policy.py": "c6c9ea8e45dfc03c4703503f4dc9c92f887bbacdbf1c7779a487567eee6c257c",
|
||||
"repository/active-runtime/app/silent_release_controller.py": "524a0a9961b15c2a35b669d1b89b2142377c13ddd3c721780f374331b7d2b6eb",
|
||||
"repository/active-runtime/app/silent_release_hardening.py": "524aba7034f2cd89d897fe01e9799c835bae9f11ca738182ce469aa59e05cc1a",
|
||||
"repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/approval.py": "cc937a81aef881206cb1d6fd2ad1feb1a1c2e0ad91c908095d56fd96402c3373",
|
||||
"repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/models.py": "aa3b3dd41c03c17e4e116ce444d9f4f779694a9cde684e8329cf892bd319069a",
|
||||
"repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/policy.py": "24b3f3326e6dcd1a4c376d892c53ad20c68e0dd6f861a3a34e11c9a2bba4c756",
|
||||
"repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/release.py": "db535e5006f8e4a3ab80c2f689213045c120b01cda9b43ef461a3022096c9d19",
|
||||
"repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/store.py": "31e1f3b904b0bfc8822d21bfd166a45388f122398d1ec76b803e90ee06dc92f0",
|
||||
"repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/workflow.py": "5f6a8e3a4d66f47717e95842f2eaa443e3cfa321cb08026cc82d74c4ad353bcf",
|
||||
"repository/tests/autonomous_release/test_core_contracts.py": "71852070db50a04901c141213cadc987ae17ec678fe27a901fee165b6d1c3e2a",
|
||||
"repository/tests/autonomous_release/test_external_gates.py": "91cca37eb1d40f0cf9fc38b12e2123045ffebd794a34d6cdef3f1dec470fffd2",
|
||||
"repository/tests/autonomous_release/test_release_gates.py": "d82b23ac00f5389f1a0de628bda9dff898d33f80d375cf6ff74f1531fd138134",
|
||||
"repository/tests/autonomous_release/test_state_and_workflow.py": "c0395a901732adb0b2f1402e6a1cf5202d54aa597bcaec9584a7ea7589c53917"
|
||||
},
|
||||
"reviewable_pre_migration_governance_identity_snapshot": [
|
||||
{
|
||||
"created_unix": 1783321252,
|
||||
"id": 1,
|
||||
"is_active": 1,
|
||||
"is_admin": 0,
|
||||
"last_login_unix": 0,
|
||||
"lower_name": "aios",
|
||||
"prohibit_login": 1,
|
||||
"type": 0
|
||||
},
|
||||
{
|
||||
"created_unix": 1783335380,
|
||||
"id": 2,
|
||||
"is_active": 1,
|
||||
"is_admin": 1,
|
||||
"last_login_unix": 1785086126,
|
||||
"lower_name": "owner",
|
||||
"prohibit_login": 0,
|
||||
"type": 0
|
||||
},
|
||||
{
|
||||
"created_unix": 1783411566,
|
||||
"id": 3,
|
||||
"is_active": 1,
|
||||
"is_admin": 1,
|
||||
"last_login_unix": 0,
|
||||
"lower_name": "gito",
|
||||
"prohibit_login": 0,
|
||||
"type": 0
|
||||
},
|
||||
{
|
||||
"created_unix": 1785078826,
|
||||
"id": 42,
|
||||
"is_active": 1,
|
||||
"is_admin": 0,
|
||||
"last_login_unix": 1785206253,
|
||||
"lower_name": "codex",
|
||||
"prohibit_login": 0,
|
||||
"type": 0
|
||||
},
|
||||
{
|
||||
"created_unix": 1785078882,
|
||||
"id": 43,
|
||||
"is_active": 1,
|
||||
"is_admin": 0,
|
||||
"last_login_unix": 1785206380,
|
||||
"lower_name": "gpt",
|
||||
"prohibit_login": 0,
|
||||
"type": 0
|
||||
}
|
||||
],
|
||||
"reviewable_pre_migration_protection_snapshot": {
|
||||
"protected_branch": [
|
||||
{
|
||||
"approvals_whitelist_team_i_ds": "null",
|
||||
"approvals_whitelist_user_i_ds": "[42,43]",
|
||||
"block_on_official_review_requests": 1,
|
||||
"block_on_outdated_branch": 1,
|
||||
"block_on_rejected_reviews": 1,
|
||||
"branch_name": "governance/approvals",
|
||||
"can_push": 0,
|
||||
"created_unix": 1785093201,
|
||||
"dismiss_stale_approvals": 1,
|
||||
"enable_approvals_whitelist": 1,
|
||||
"enable_merge_whitelist": 1,
|
||||
"enable_status_check": 0,
|
||||
"enable_whitelist": 0,
|
||||
"id": 1,
|
||||
"ignore_stale_approvals": 0,
|
||||
"merge_whitelist_team_i_ds": "null",
|
||||
"merge_whitelist_user_i_ds": "[42,43]",
|
||||
"protected_file_patterns": "",
|
||||
"repo_id": 1,
|
||||
"require_signed_commits": 0,
|
||||
"required_approvals": 2,
|
||||
"status_check_contexts": "[]",
|
||||
"unprotected_file_patterns": "",
|
||||
"updated_unix": 1785093422,
|
||||
"whitelist_deploy_keys": 0,
|
||||
"whitelist_team_i_ds": "null",
|
||||
"whitelist_user_i_ds": "null"
|
||||
},
|
||||
{
|
||||
"approvals_whitelist_team_i_ds": "null",
|
||||
"approvals_whitelist_user_i_ds": "[42,43]",
|
||||
"block_on_official_review_requests": 1,
|
||||
"block_on_outdated_branch": 1,
|
||||
"block_on_rejected_reviews": 1,
|
||||
"branch_name": "integration/chatgpt-autonomous-completion-20260726",
|
||||
"can_push": 0,
|
||||
"created_unix": 1785093201,
|
||||
"dismiss_stale_approvals": 1,
|
||||
"enable_approvals_whitelist": 1,
|
||||
"enable_merge_whitelist": 1,
|
||||
"enable_status_check": 0,
|
||||
"enable_whitelist": 0,
|
||||
"id": 3,
|
||||
"ignore_stale_approvals": 0,
|
||||
"merge_whitelist_team_i_ds": "null",
|
||||
"merge_whitelist_user_i_ds": "[42,43]",
|
||||
"protected_file_patterns": "",
|
||||
"repo_id": 1,
|
||||
"require_signed_commits": 0,
|
||||
"required_approvals": 2,
|
||||
"status_check_contexts": "[]",
|
||||
"unprotected_file_patterns": "",
|
||||
"updated_unix": 1785093422,
|
||||
"whitelist_deploy_keys": 0,
|
||||
"whitelist_team_i_ds": "null",
|
||||
"whitelist_user_i_ds": "null"
|
||||
},
|
||||
{
|
||||
"approvals_whitelist_team_i_ds": "null",
|
||||
"approvals_whitelist_user_i_ds": "[42,43]",
|
||||
"block_on_official_review_requests": 1,
|
||||
"block_on_outdated_branch": 1,
|
||||
"block_on_rejected_reviews": 1,
|
||||
"branch_name": "main",
|
||||
"can_push": 0,
|
||||
"created_unix": 1785093201,
|
||||
"dismiss_stale_approvals": 1,
|
||||
"enable_approvals_whitelist": 1,
|
||||
"enable_merge_whitelist": 1,
|
||||
"enable_status_check": 0,
|
||||
"enable_whitelist": 0,
|
||||
"id": 2,
|
||||
"ignore_stale_approvals": 0,
|
||||
"merge_whitelist_team_i_ds": "null",
|
||||
"merge_whitelist_user_i_ds": "[42,43]",
|
||||
"protected_file_patterns": "",
|
||||
"repo_id": 1,
|
||||
"require_signed_commits": 0,
|
||||
"required_approvals": 2,
|
||||
"status_check_contexts": "[]",
|
||||
"unprotected_file_patterns": "",
|
||||
"updated_unix": 1785093422,
|
||||
"whitelist_deploy_keys": 0,
|
||||
"whitelist_team_i_ds": "null",
|
||||
"whitelist_user_i_ds": "null"
|
||||
}
|
||||
],
|
||||
"protected_tag": []
|
||||
},
|
||||
"rollback_archive_members": {
|
||||
"pre-migration-backup/app.ini": "0693e4fa8296a22533429339faeee2ac1856b6c966c6c1009bb139ab4784f687",
|
||||
"pre-migration-backup/authoritative-aios.bundle": "4fb4ccc979c3061065cde451af0e733326cb56fa6f860124bb73f153a60907c9",
|
||||
"pre-migration-backup/authoritative-refs.txt": "85cb664c8edf3902bebfc6c2253a3ee16cff8534f86561ba771a73df160186c6",
|
||||
"pre-migration-backup/backup-metadata.json": "a38ef6dacb52ae61c4332bf8d087f479fb0d5f51d5f636a132a711bddd6e7732",
|
||||
"pre-migration-backup/checksums.sha256": "72e2fe0b03d1011c872bb39a6cff489f08398ac7d52092f720cbbc63bcf6f753",
|
||||
"pre-migration-backup/gitea-aios.bundle": "a343c7437f3a1a2aa842dd9d986a1d3cf80691058faae978565224320ff5ed90",
|
||||
"pre-migration-backup/gitea-refs.txt": "1792b2c4f1cf1f232704601ee968e16425f926a6eaab1bdcddf273525d046e4d",
|
||||
"pre-migration-backup/gitea.db": "aadcc1587a05827ed3b2c4ffc2403f26139020cd0328afaa03f7afbf6083a2f1",
|
||||
"pre-migration-backup/governance-identities.json": "2d7b4e72d1fdf187425092089589547e0d76784c611d00f915a8c212f17d3b15",
|
||||
"pre-migration-backup/protected-branch.json": "5cc4ae4fe5614baa3d4bb83d71872a4ad1ce3c5e39061e02b336be40d5b5b75d",
|
||||
"pre-migration-backup/protected-tag.json": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"pre-migration-backup/repository/active-runtime/app/aios_isolated_writer_policy.py": "d86ec23fd5d8b7e9844c0895c50385b6f8f3071c3dac3c0f26c70768ae00a188",
|
||||
"pre-migration-backup/repository/active-runtime/app/isolated_writer_authz.py": "cfae1cdf70c1dabbe013e9365c308a45f11acf20035881a3bb6143774682bbf0",
|
||||
"pre-migration-backup/repository/active-runtime/app/isolated_writer_path_policy.py": "c6c9ea8e45dfc03c4703503f4dc9c92f887bbacdbf1c7779a487567eee6c257c",
|
||||
"pre-migration-backup/repository/active-runtime/app/silent_release_controller.py": "524a0a9961b15c2a35b669d1b89b2142377c13ddd3c721780f374331b7d2b6eb",
|
||||
"pre-migration-backup/repository/active-runtime/app/silent_release_hardening.py": "524aba7034f2cd89d897fe01e9799c835bae9f11ca738182ce469aa59e05cc1a",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/approval.py": "cc937a81aef881206cb1d6fd2ad1feb1a1c2e0ad91c908095d56fd96402c3373",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/models.py": "aa3b3dd41c03c17e4e116ce444d9f4f779694a9cde684e8329cf892bd319069a",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/policy.py": "24b3f3326e6dcd1a4c376d892c53ad20c68e0dd6f861a3a34e11c9a2bba4c756",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/release.py": "db535e5006f8e4a3ab80c2f689213045c120b01cda9b43ef461a3022096c9d19",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/store.py": "31e1f3b904b0bfc8822d21bfd166a45388f122398d1ec76b803e90ee06dc92f0",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/workflow.py": "5f6a8e3a4d66f47717e95842f2eaa443e3cfa321cb08026cc82d74c4ad353bcf",
|
||||
"pre-migration-backup/repository/tests/autonomous_release/test_core_contracts.py": "71852070db50a04901c141213cadc987ae17ec678fe27a901fee165b6d1c3e2a",
|
||||
"pre-migration-backup/repository/tests/autonomous_release/test_external_gates.py": "91cca37eb1d40f0cf9fc38b12e2123045ffebd794a34d6cdef3f1dec470fffd2",
|
||||
"pre-migration-backup/repository/tests/autonomous_release/test_release_gates.py": "d82b23ac00f5389f1a0de628bda9dff898d33f80d375cf6ff74f1531fd138134",
|
||||
"pre-migration-backup/repository/tests/autonomous_release/test_state_and_workflow.py": "c0395a901732adb0b2f1402e6a1cf5202d54aa597bcaec9584a7ea7589c53917"
|
||||
},
|
||||
"rollback_configuration_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-MIGRATION-20260729/rollback-configuration.tar",
|
||||
"rollback_configuration_sha256": "fc8dcf68aaf53edb0cda45833ee45ba5b8b205791af9073258ca617c8b8187d6",
|
||||
"rollback_restores_protection_before_validator": true
|
||||
},
|
||||
"auto_merge_after_owner_approval": false,
|
||||
"auto_merge_machine_conditions": [
|
||||
"owner_approval_valid",
|
||||
"head_oid_unchanged",
|
||||
"required_checks_pass",
|
||||
"secret_scan_pass",
|
||||
"evidence_closure_pass",
|
||||
"approval_not_expired",
|
||||
"protected_branch_rules_pass"
|
||||
],
|
||||
"automatic_renewal_policy": {
|
||||
"approval_ref_fast_forward_descendant_required": true,
|
||||
"changed_condition_fallback": "HUMAN_OWNER_APPROVAL_PENDING",
|
||||
"secret_scan": "PASS_REQUIRED",
|
||||
"single_renewal_max_hours": 12,
|
||||
"status": "PROPOSED_AFTER_MIGRATION",
|
||||
"total_window_max_days": 7,
|
||||
"unchanged_fields_required": [
|
||||
"original_policy_sha256",
|
||||
"allowed_paths",
|
||||
"allowed_operations",
|
||||
"denied_operations",
|
||||
"source_sha256",
|
||||
"source_blob_oid"
|
||||
]
|
||||
},
|
||||
"break_glass_executor_may_merge_only_after_owner_approval_machine_gates_and_atomic_cutover_preconditions": true,
|
||||
"break_glass_expires_at": "2026-07-29T16:22:46Z",
|
||||
"desired_branch_protection": {
|
||||
"approval_whitelist_user_ids": [
|
||||
2
|
||||
],
|
||||
"approval_whitelist_usernames": [
|
||||
"owner"
|
||||
],
|
||||
"block_on_official_review_requests": true,
|
||||
"block_on_outdated_branch": true,
|
||||
"block_on_rejected_reviews": true,
|
||||
"branch_deletion": false,
|
||||
"branch_patterns": [
|
||||
"governance/approvals",
|
||||
"main",
|
||||
"release/**",
|
||||
"integration/**"
|
||||
],
|
||||
"direct_push": false,
|
||||
"dismiss_stale_approvals": true,
|
||||
"force_push": false,
|
||||
"require_status_checks": true,
|
||||
"required_approvals": 1,
|
||||
"required_status_contexts": [
|
||||
"aios/machine-gates",
|
||||
"aios/secret-scan",
|
||||
"aios/evidence-closure"
|
||||
],
|
||||
"wip_blocks_merge": true
|
||||
},
|
||||
"executor_must_not_approve": true,
|
||||
"expected_governance_base_oid": "ac2dc5c30eb35748ef11c98297812729d0fd5828",
|
||||
"expires_at": "2026-07-29T16:22:46Z",
|
||||
"global_constraints": {
|
||||
"AIOS_GITOPS_WRITES_ENABLED": false,
|
||||
"approval_impersonation": false,
|
||||
"direct_main_push": false,
|
||||
"fake_review": false,
|
||||
"force_push": false,
|
||||
"production_change_without_owner_approval": false,
|
||||
"secret_commit": false,
|
||||
"tag_rewrite": false
|
||||
},
|
||||
"governance_model": "SINGLE_OWNER_WITH_MACHINE_GATES_V1",
|
||||
"historical_policy": {
|
||||
"historical_manifests_modified": false,
|
||||
"old_policy_decoder_retained_read_only": true,
|
||||
"requests_created_before_policy_effective_at_use_old_policy": true
|
||||
},
|
||||
"issued_at": "2026-07-28T16:22:46Z",
|
||||
"machine_gates": {
|
||||
"development_branch": [
|
||||
"allowed_branch",
|
||||
"allowed_paths",
|
||||
"compileall",
|
||||
"targeted_tests",
|
||||
"secret_scan",
|
||||
"git_diff_check"
|
||||
],
|
||||
"production": [
|
||||
"owner_approval",
|
||||
"tag_consistency",
|
||||
"registry_consistency",
|
||||
"deployment_plan",
|
||||
"rollback_plan",
|
||||
"artifact_integrity",
|
||||
"production_preflight"
|
||||
],
|
||||
"protected_branch_merge": [
|
||||
"expected_base_oid",
|
||||
"current_head_oid",
|
||||
"ordinary_merge_commit",
|
||||
"parent_order_verified",
|
||||
"artifact_binding",
|
||||
"audit_chain_valid"
|
||||
],
|
||||
"release": [
|
||||
"version_manifest",
|
||||
"explicit_release_target_oid",
|
||||
"artifact_sha256",
|
||||
"rollback_artifact_sha256",
|
||||
"sbom",
|
||||
"test_evidence",
|
||||
"evidence_closure"
|
||||
]
|
||||
},
|
||||
"merge_mode": "CREATE_MERGE_COMMIT",
|
||||
"migration_acceptance_tests": [
|
||||
"agent_request_pass",
|
||||
"owner_current_head_approval_pass",
|
||||
"machine_gates_then_merge_commit_pass",
|
||||
"agent_self_approval_denied",
|
||||
"non_owner_approval_denied",
|
||||
"stale_head_approval_denied",
|
||||
"wip_merge_denied",
|
||||
"machine_test_failure_merge_denied",
|
||||
"secret_scan_failure_merge_denied",
|
||||
"main_direct_write_denied",
|
||||
"tag_direct_write_denied",
|
||||
"force_push_denied",
|
||||
"missing_tag_target_oid_denied",
|
||||
"missing_evidence_closure_oid_denied",
|
||||
"production_without_owner_denied",
|
||||
"dormant_only_production_activation_denied"
|
||||
],
|
||||
"migration_mode": "OWNER_BREAK_GLASS_GOVERNANCE_RESET",
|
||||
"new_policy": "SINGLE_OWNER_WITH_MACHINE_GATES_V1",
|
||||
"old_policy": "DUAL_HUMAN_APPROVAL",
|
||||
"owner_approval_contract": {
|
||||
"dismissed": false,
|
||||
"does_not_claim_old_dual_approval_compliance": true,
|
||||
"head_change_invalidates_owner_approval": true,
|
||||
"owner_review_is_break_glass_signature": true,
|
||||
"review_created_before_approval_expires_at": true,
|
||||
"review_state": "APPROVED",
|
||||
"reviewed_head_oid_must_equal_current_pr_head_oid": true,
|
||||
"reviewer_must_equal": "owner",
|
||||
"stale": false
|
||||
},
|
||||
"owner_break_glass_declaration_to_be_attested_by_review": {
|
||||
"attestation_status": "PENDING_OWNER_REVIEW",
|
||||
"expires_at": "2026-07-29T16:22:46Z",
|
||||
"new_policy": "SINGLE_OWNER_WITH_MACHINE_GATES_V1",
|
||||
"old_policy": "DUAL_HUMAN_APPROVAL",
|
||||
"owner_acknowledges_old_policy_exception": true,
|
||||
"reason": "single-maintainer-system",
|
||||
"scope": "governance-policy-migration-only"
|
||||
},
|
||||
"owner_is_only_human_approver": true,
|
||||
"policy_effective_at": null,
|
||||
"pre_approval_non_authorizations": {
|
||||
"approval_validator_update_allowed": false,
|
||||
"gitea_branch_protection_update_allowed": false,
|
||||
"merge_allowed": false,
|
||||
"old_dual_rule_disable_allowed": false,
|
||||
"production_change_allowed": false,
|
||||
"state_machine_update_allowed": false,
|
||||
"writer_credential_issue_allowed": false
|
||||
},
|
||||
"prohibited_merge_modes": [
|
||||
"squash",
|
||||
"rebase",
|
||||
"force",
|
||||
"fast_forward_policy_bypass"
|
||||
],
|
||||
"reason": "single-maintainer-system",
|
||||
"repository": "aios/aios",
|
||||
"request_status": "HUMAN_OWNER_APPROVAL_PENDING",
|
||||
"requester_must_not_approve": true,
|
||||
"requesters": [
|
||||
"codex-chartgpt-bridge",
|
||||
"aios-governance-agent",
|
||||
"aios-release-agent",
|
||||
"aios-governed-development-writer"
|
||||
],
|
||||
"required_distinct_human_approvers": 1,
|
||||
"required_human_approvals": 1,
|
||||
"required_owner_approvals": 1,
|
||||
"resolved_owner": {
|
||||
"confidence_before_review": "MEDIUM",
|
||||
"confirmation_rule": "valid APPROVED review by owner on this exact immutable head",
|
||||
"email_sha256": "f5dc2928f6202c24fa4db8bb33db84e1b77c6eff30d1b95abdadeaae209bc4c9",
|
||||
"identity_source": [
|
||||
"gitea_repository_owner_account_aios_is_login_prohibited",
|
||||
"all_login_enabled_site_admins_enumerated_owner_and_never_logged_in_service_gito",
|
||||
"only_interactive_site_admin_named_owner",
|
||||
"gitea_owner_last_login_present",
|
||||
"historical_governance_records_use_approved_by_role_owner",
|
||||
"user_declared_single_natural_person_maintainer"
|
||||
],
|
||||
"login_enabled_site_admins": [
|
||||
{
|
||||
"observed_prior_login": true,
|
||||
"user_id": 2,
|
||||
"username": "owner"
|
||||
},
|
||||
{
|
||||
"classification": "service_admin_not_human_approver",
|
||||
"observed_prior_login": false,
|
||||
"user_id": 3,
|
||||
"username": "gito"
|
||||
}
|
||||
],
|
||||
"user_id": 2,
|
||||
"username": "owner"
|
||||
},
|
||||
"schema": "aios.single-owner-governance-policy/v1",
|
||||
"scope": "governance-policy-migration-only",
|
||||
"standing_autonomous_development_policy": {
|
||||
"allowed_without_repeated_owner_approval": [
|
||||
"patch_branch_create",
|
||||
"allowlisted_development_write",
|
||||
"patch_branch_commit_and_push",
|
||||
"test_execute",
|
||||
"artifact_build",
|
||||
"approval_pr_create",
|
||||
"read_only_evidence",
|
||||
"task_writer_renewal"
|
||||
],
|
||||
"owner_approval_still_required": [
|
||||
"protected_branch_merge",
|
||||
"tag",
|
||||
"registry_update",
|
||||
"production_baseline_update",
|
||||
"production_deploy",
|
||||
"production_rollback",
|
||||
"governance_policy_change"
|
||||
],
|
||||
"status": "PROPOSED_AFTER_MIGRATION"
|
||||
},
|
||||
"standing_single_owner_governance_migration_authorization": true,
|
||||
"state_machine": {
|
||||
"auto_merge_ready_state": "AUTO_MERGE_READY",
|
||||
"execution_ready_state": "OWNER_APPROVED_EXECUTION_READY",
|
||||
"machine_verified_state": "MACHINE_GATES_VERIFIED",
|
||||
"owner_approval_alone_is_not_execution_ready": true,
|
||||
"owner_verified_state": "OWNER_APPROVAL_VERIFIED",
|
||||
"pending_state": "HUMAN_OWNER_APPROVAL_PENDING"
|
||||
},
|
||||
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-MIGRATION-20260729",
|
||||
"this_manifest_authorizes_only_governance_policy_migration": true,
|
||||
"this_manifest_grants_standing_development_authorization": false
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"schema": "aios.gitea-native-approval/v1",
|
||||
"approval_type": "IMPLEMENTATION",
|
||||
"task_id": "AIOS-INTEGRATION-REFRESH-PROVISIONER-IMPLEMENTATION-20260726",
|
||||
"repository": "aios/aios",
|
||||
"branch": "patch/integration-refresh-grant-provisioner-20260726",
|
||||
"base_oid": "875d41c3101093149414292067fd700dc214d4d3",
|
||||
"actor_identity": "chatgpt-gitops-bot",
|
||||
"writer_audience": "aios-integration-refresh-provisioner-development-writer",
|
||||
"risk": "high",
|
||||
"allowed_paths": [
|
||||
"active-runtime/app/**",
|
||||
"tests/test_integration_refresh_provisioner.py",
|
||||
"tests/test_isolated_writer_integration_merge.py",
|
||||
"tests/test_gitea_branch_read_projection.py",
|
||||
"tests/test_isolated_writer_token_policy_v2.py",
|
||||
"deploy/systemd/aios-integration-refresh-*.service",
|
||||
"docs/autonomous-release/**"
|
||||
],
|
||||
"allowed_operations": [
|
||||
"repository.source_map.read",
|
||||
"runtime.unit_metadata.read",
|
||||
"git.ref.read",
|
||||
"git.diff.read",
|
||||
"git.branch.patch.create",
|
||||
"commit_file.atomic",
|
||||
"git.push.patch_branch",
|
||||
"test.execute.allowlisted",
|
||||
"artifact.build.control_plane_patch",
|
||||
"evidence.write.task_runtime"
|
||||
],
|
||||
"denied_operations": [
|
||||
"main.write",
|
||||
"main.merge",
|
||||
"tag.write",
|
||||
"tag.delete",
|
||||
"tag.rewrite",
|
||||
"force_push",
|
||||
"history.rewrite",
|
||||
"baseline.write",
|
||||
"registry.write",
|
||||
"production.deploy",
|
||||
"production.rollback",
|
||||
"secret.read",
|
||||
"secret.export",
|
||||
"bare_ref_direct_write"
|
||||
],
|
||||
"expires_at": "2026-07-27T03:35:55Z"
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"schema": "aios.gitea-native-approval/v1",
|
||||
"approval_type": "IMPLEMENTATION",
|
||||
"task_id": "AIOS-INTEGRATION-REFRESH-PROVISIONER-IMPLEMENTATION-20260726",
|
||||
"repository": "aios/aios",
|
||||
"branch": "patch/integration-refresh-grant-provisioner-20260726",
|
||||
"base_oid": "875d41c3101093149414292067fd700dc214d4d3",
|
||||
"actor_identity": "chatgpt-gitops-bot",
|
||||
"writer_audience": "aios-integration-refresh-provisioner-development-writer",
|
||||
"risk": "high",
|
||||
"allowed_paths": [
|
||||
"active-runtime/app/**",
|
||||
"tests/test_integration_refresh_provisioner.py",
|
||||
"tests/test_isolated_writer_integration_merge.py",
|
||||
"tests/test_gitea_branch_read_projection.py",
|
||||
"tests/test_isolated_writer_token_policy_v2.py",
|
||||
"deploy/systemd/aios-integration-refresh-*.service",
|
||||
"docs/autonomous-release/**"
|
||||
],
|
||||
"allowed_operations": [
|
||||
"repository.source_map.read",
|
||||
"runtime.unit_metadata.read",
|
||||
"git.ref.read",
|
||||
"git.diff.read",
|
||||
"git.branch.patch.create",
|
||||
"commit_file.atomic",
|
||||
"git.push.patch_branch",
|
||||
"test.execute.allowlisted",
|
||||
"artifact.build.control_plane_patch",
|
||||
"evidence.write.task_runtime"
|
||||
],
|
||||
"denied_operations": [
|
||||
"main.write",
|
||||
"main.merge",
|
||||
"tag.write",
|
||||
"tag.delete",
|
||||
"tag.rewrite",
|
||||
"force_push",
|
||||
"history.rewrite",
|
||||
"baseline.write",
|
||||
"registry.write",
|
||||
"production.deploy",
|
||||
"production.rollback",
|
||||
"secret.read",
|
||||
"secret.export",
|
||||
"bare_ref_direct_write"
|
||||
],
|
||||
"expires_at": "2026-07-27T13:00:11Z"
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"approval_type": "SINGLE_OWNER_GOVERNANCE_LIVE_ACTIVATION_IMPLEMENTATION_AND_EXECUTION",
|
||||
"artifact_sha256": "01e0226f0302bb8e2f8d81299393075bf0332c6e22e00877469fb297c51f3802",
|
||||
"authorization_scope": [
|
||||
"deploy_live_activation_implementation",
|
||||
"execute_one_atomic_governance_activation",
|
||||
"execute_acceptance_tests",
|
||||
"control_plane_rollback_on_failure",
|
||||
"write_activation_receipt"
|
||||
],
|
||||
"denied_operations": [
|
||||
"main.write",
|
||||
"integration.write",
|
||||
"tag.write",
|
||||
"registry.write",
|
||||
"baseline.write",
|
||||
"production.deploy",
|
||||
"force_push",
|
||||
"history_rewrite"
|
||||
],
|
||||
"evidence_closure_oid": "c6316943d2485f06f97242df6338ded21de1ee38",
|
||||
"evidence_closure_sha256": "bd521103c907d6194acbbbd42a947c5af2ccbda91797e5dc75e81887a042becd",
|
||||
"execution_binding": {
|
||||
"backend": "sqlite",
|
||||
"branch": "governance/approvals",
|
||||
"evidence_closure_oid": "c6316943d2485f06f97242df6338ded21de1ee38",
|
||||
"executor": "aios-governance-single-owner-activation",
|
||||
"expected_branch_protection_sha256": "f27992c8024426708d93bce072d231707bae47a927aa2393d566aae64d5ed079",
|
||||
"expected_gitea_schema_sha256": "3524f4912165939cb746c2c65d70583a909f5abbd00c4e07ae76f7206d49aa54",
|
||||
"expected_state_machine_sha256": "0b08a2c378d143cb9031a25132afc7b7d825bdc03d11efe10610341d4b70167e",
|
||||
"expected_validator_sha256": "754639846cb15cb997d612a1fe0ceb0224ddef5bbc3ccac9802a3747f122dac1",
|
||||
"forbidden_operations": {
|
||||
"baseline": false,
|
||||
"force_push": false,
|
||||
"governance_direct": false,
|
||||
"history_rewrite": false,
|
||||
"integration": false,
|
||||
"main": false,
|
||||
"production": false,
|
||||
"registry": false,
|
||||
"tags": false
|
||||
},
|
||||
"operation_id": "single-owner-live-activation-20260729T033228Z",
|
||||
"repo_id": 1,
|
||||
"requester": "codex-agent",
|
||||
"target_branch_protection_sha256": "61b424b0c495eed71c73171c5c4434ac66d074de36886a73ec4c445fd1905db8",
|
||||
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729"
|
||||
},
|
||||
"execution_binding_sha256": "6b2ed671a94fa83704383728fd2bb2df3b67523b4273100fa371777d984be102",
|
||||
"expected_governance_base_oid": "8b7f9d9028f6ff9beb225bf85f53dbeaac6830e7",
|
||||
"expires_at": "2026-07-29T15:32:28Z",
|
||||
"human_authorizer": "owner",
|
||||
"implementation_commit_oid": "e1e438564ce8b987a36df0947e6eaa324dfede74",
|
||||
"implementation_tree_oid": "dd93dc1df10cb8b8fe59b09d3214a9ae6fee671e",
|
||||
"issued_at": "2026-07-29T03:32:28Z",
|
||||
"legacy_mechanical_approvals": [
|
||||
"codex",
|
||||
"gpt"
|
||||
],
|
||||
"machine_gate_contexts": [
|
||||
"aios/branch-protection",
|
||||
"aios/evidence-closure",
|
||||
"aios/secret-scan",
|
||||
"aios/state-machine",
|
||||
"aios/validator"
|
||||
],
|
||||
"pr30_merge_oid": "f6bef90633f428454e85721f353cd82cdf04caf0",
|
||||
"pr31_merge_oid": "8b7f9d9028f6ff9beb225bf85f53dbeaac6830e7",
|
||||
"rollback_artifact_sha256": "ed9307cc2f07767b75866fcb370fa3f98b76c5147e4b7fc49ecf04cf21cf7c31",
|
||||
"signed_merge_requirement_effective": false,
|
||||
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729",
|
||||
"test_evidence_sha256": "172a7cf812c6b901524138fb72851a9b7d2e895dedc98893407d0bab9b52c6c1"
|
||||
}
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
{
|
||||
"approval_type": "SINGLE_OWNER_GOVERNANCE_LIVE_ACTIVATION_IMPLEMENTATION_AND_EXECUTION",
|
||||
"artifact_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729/artifacts/hardening-71def340e7cc34f4f19918dcf8d3c847abbe4fbf/live-activation-71def340e7cc34f4f19918dcf8d3c847abbe4fbf.tar.gz",
|
||||
"artifact_sha256": "32bc49c207ec454b6fd81b0484f5b257bb789481b37175995029a2ec78cc62fa",
|
||||
"authorization_scope": [
|
||||
"deploy_exact_approved_artifact",
|
||||
"publish_exact_head_machine_statuses",
|
||||
"execute_one_atomic_governance_activation",
|
||||
"verify_and_rollback_exact_transaction"
|
||||
],
|
||||
"denied_operations": [
|
||||
"main.write",
|
||||
"integration.write",
|
||||
"tag.write",
|
||||
"registry.write",
|
||||
"baseline.write",
|
||||
"production.deploy",
|
||||
"force_push",
|
||||
"history_rewrite"
|
||||
],
|
||||
"evidence_closure_blob_oid": "6af6d017f22c4e0c512b0cd62644cdc6ab2135d5",
|
||||
"evidence_closure_oid": "310ab2af902a9fde9108d227077f433cc743ce7c",
|
||||
"evidence_closure_path": "evidence/single-owner-governance/AIOS-SINGLE-OWNER-LIVE-ACTIVATION-HARDENING-20260729T043010Z.json",
|
||||
"evidence_closure_sha256": "e482fe52c8381f765ee508d2de1fe2e77dff52e6e342f01be4ca481ff2f76820",
|
||||
"execution_binding": {
|
||||
"artifact_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729/artifacts/hardening-71def340e7cc34f4f19918dcf8d3c847abbe4fbf/live-activation-71def340e7cc34f4f19918dcf8d3c847abbe4fbf.tar.gz",
|
||||
"artifact_sha256": "32bc49c207ec454b6fd81b0484f5b257bb789481b37175995029a2ec78cc62fa",
|
||||
"backend": "sqlite",
|
||||
"branch": "governance/approvals",
|
||||
"evidence_closure_oid": "310ab2af902a9fde9108d227077f433cc743ce7c",
|
||||
"evidence_closure_path": "evidence/single-owner-governance/AIOS-SINGLE-OWNER-LIVE-ACTIVATION-HARDENING-20260729T043010Z.json",
|
||||
"evidence_closure_sha256": "e482fe52c8381f765ee508d2de1fe2e77dff52e6e342f01be4ca481ff2f76820",
|
||||
"executor": "aios-governance-single-owner-activation",
|
||||
"expected_branch_protection_sha256": "f27992c8024426708d93bce072d231707bae47a927aa2393d566aae64d5ed079",
|
||||
"expected_gitea_schema_sha256": "3524f4912165939cb746c2c65d70583a909f5abbd00c4e07ae76f7206d49aa54",
|
||||
"expected_state_machine_sha256": "0b08a2c378d143cb9031a25132afc7b7d825bdc03d11efe10610341d4b70167e",
|
||||
"expected_validator_sha256": "754639846cb15cb997d612a1fe0ceb0224ddef5bbc3ccac9802a3747f122dac1",
|
||||
"forbidden_operations": {
|
||||
"baseline": false,
|
||||
"force_push": false,
|
||||
"governance_direct": false,
|
||||
"history_rewrite": false,
|
||||
"integration": false,
|
||||
"main": false,
|
||||
"production": false,
|
||||
"registry": false,
|
||||
"tags": false
|
||||
},
|
||||
"implementation_commit_oid": "71def340e7cc34f4f19918dcf8d3c847abbe4fbf",
|
||||
"implementation_tree_oid": "3cc61bfbf69cbe0a24d54a0e53a6c50bcc130066",
|
||||
"machine_status_producer_id": 13,
|
||||
"machine_status_producer_name": "gitops-bot",
|
||||
"operation_id": "single-owner-live-activation-hardening-20260729T043149Z",
|
||||
"repo_id": 1,
|
||||
"requester": "codex-agent",
|
||||
"rollback_artifact_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729/artifacts/hardening-71def340e7cc34f4f19918dcf8d3c847abbe4fbf/live-activation-rollback-71def340e7cc34f4f19918dcf8d3c847abbe4fbf.tar.gz",
|
||||
"rollback_artifact_sha256": "4efe1ea73d4fd72e5364fc91a16ad5e9d1e6ebe78d8aa466f7b11c4915b08403",
|
||||
"runtime_sources": [
|
||||
{
|
||||
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/activation.py",
|
||||
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/activation.py",
|
||||
"sha256": "48846a58fa3afb2b7c8ffe6e88504a9333863932df867f33ba0321ad3bac6003"
|
||||
},
|
||||
{
|
||||
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/gitea_protected_branch.py",
|
||||
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/gitea_protected_branch.py",
|
||||
"sha256": "00775efe3f3d631d88379eff4ed2dcfaf386a2fa43111abe961e75109e80d768"
|
||||
},
|
||||
{
|
||||
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/journal.py",
|
||||
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/journal.py",
|
||||
"sha256": "a8f121245e04487d99c99fb03ada83f0c907723f8eb18552872ff46dcf5a7873"
|
||||
},
|
||||
{
|
||||
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/state_machine.py",
|
||||
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/state_machine.py",
|
||||
"sha256": "0b08a2c378d143cb9031a25132afc7b7d825bdc03d11efe10610341d4b70167e"
|
||||
},
|
||||
{
|
||||
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/validator.py",
|
||||
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/validator.py",
|
||||
"sha256": "754639846cb15cb997d612a1fe0ceb0224ddef5bbc3ccac9802a3747f122dac1"
|
||||
}
|
||||
],
|
||||
"target_branch_protection_sha256": "4713f0494b79103668fbe5bd8fd7da5d60a43e56fb545aa6e7c773f43dc07c10",
|
||||
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729",
|
||||
"test_evidence_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729/artifacts/hardening-71def340e7cc34f4f19918dcf8d3c847abbe4fbf/test-evidence-71def340e7cc34f4f19918dcf8d3c847abbe4fbf.json",
|
||||
"test_evidence_sha256": "c1633732767f3f051ffb01404d12d05a6168a4b01b91efdb34a64988d6dee684"
|
||||
},
|
||||
"execution_binding_sha256": "63705dd0f5cc25debde132179f96819a844a9703b33c092db4ac48d5680d536a",
|
||||
"expected_governance_base_oid": "1071979079deab8a4028473d967c2a205c3b111e",
|
||||
"expires_at": "2026-07-29T16:31:49Z",
|
||||
"human_authorizer": "owner",
|
||||
"implementation_commit_oid": "71def340e7cc34f4f19918dcf8d3c847abbe4fbf",
|
||||
"implementation_tree_oid": "3cc61bfbf69cbe0a24d54a0e53a6c50bcc130066",
|
||||
"issued_at": "2026-07-29T04:31:49Z",
|
||||
"legacy_mechanical_approvals": [
|
||||
"codex",
|
||||
"gpt"
|
||||
],
|
||||
"machine_gate_contexts": [
|
||||
"aios/branch-protection",
|
||||
"aios/evidence-closure",
|
||||
"aios/secret-scan",
|
||||
"aios/state-machine",
|
||||
"aios/validator"
|
||||
],
|
||||
"machine_status_producer_id": 13,
|
||||
"machine_status_producer_name": "gitops-bot",
|
||||
"rollback_artifact_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729/artifacts/hardening-71def340e7cc34f4f19918dcf8d3c847abbe4fbf/live-activation-rollback-71def340e7cc34f4f19918dcf8d3c847abbe4fbf.tar.gz",
|
||||
"rollback_artifact_sha256": "4efe1ea73d4fd72e5364fc91a16ad5e9d1e6ebe78d8aa466f7b11c4915b08403",
|
||||
"runtime_sources": [
|
||||
{
|
||||
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/activation.py",
|
||||
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/activation.py",
|
||||
"sha256": "48846a58fa3afb2b7c8ffe6e88504a9333863932df867f33ba0321ad3bac6003"
|
||||
},
|
||||
{
|
||||
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/gitea_protected_branch.py",
|
||||
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/gitea_protected_branch.py",
|
||||
"sha256": "00775efe3f3d631d88379eff4ed2dcfaf386a2fa43111abe961e75109e80d768"
|
||||
},
|
||||
{
|
||||
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/journal.py",
|
||||
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/journal.py",
|
||||
"sha256": "a8f121245e04487d99c99fb03ada83f0c907723f8eb18552872ff46dcf5a7873"
|
||||
},
|
||||
{
|
||||
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/state_machine.py",
|
||||
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/state_machine.py",
|
||||
"sha256": "0b08a2c378d143cb9031a25132afc7b7d825bdc03d11efe10610341d4b70167e"
|
||||
},
|
||||
{
|
||||
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/validator.py",
|
||||
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/validator.py",
|
||||
"sha256": "754639846cb15cb997d612a1fe0ceb0224ddef5bbc3ccac9802a3747f122dac1"
|
||||
}
|
||||
],
|
||||
"target_branch_protection": {
|
||||
"approvals_whitelist_team_i_ds": "null",
|
||||
"approvals_whitelist_user_i_ds": "[2]",
|
||||
"block_on_official_review_requests": 1,
|
||||
"block_on_outdated_branch": 1,
|
||||
"block_on_rejected_reviews": 1,
|
||||
"branch_name": "governance/approvals",
|
||||
"can_push": 0,
|
||||
"created_unix": 1785093201,
|
||||
"dismiss_stale_approvals": 1,
|
||||
"enable_approvals_whitelist": 1,
|
||||
"enable_merge_whitelist": 1,
|
||||
"enable_status_check": 1,
|
||||
"enable_whitelist": 0,
|
||||
"id": 1,
|
||||
"ignore_stale_approvals": 0,
|
||||
"merge_whitelist_team_i_ds": "null",
|
||||
"merge_whitelist_user_i_ds": "[42,43]",
|
||||
"protected_file_patterns": "",
|
||||
"repo_id": 1,
|
||||
"require_signed_commits": 0,
|
||||
"required_approvals": 1,
|
||||
"status_check_contexts": "[\"aios/branch-protection\",\"aios/evidence-closure\",\"aios/secret-scan\",\"aios/state-machine\",\"aios/validator\"]",
|
||||
"unprotected_file_patterns": "",
|
||||
"updated_unix": 1785299509,
|
||||
"whitelist_deploy_keys": 0,
|
||||
"whitelist_team_i_ds": "null",
|
||||
"whitelist_user_i_ds": "null"
|
||||
},
|
||||
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729",
|
||||
"test_evidence_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729/artifacts/hardening-71def340e7cc34f4f19918dcf8d3c847abbe4fbf/test-evidence-71def340e7cc34f4f19918dcf8d3c847abbe4fbf.json",
|
||||
"test_evidence_sha256": "c1633732767f3f051ffb01404d12d05a6168a4b01b91efdb34a64988d6dee684"
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
{
|
||||
"actor_identity": "chatgpt-gitops-bot",
|
||||
"allowed_operations": [
|
||||
"runtime.source.read.exact",
|
||||
"runtime.source.hash",
|
||||
"runtime.unit_metadata.read",
|
||||
"package.ownership.read",
|
||||
"git.object.lookup.by_blob_oid",
|
||||
"secret.scan.local_source",
|
||||
"repository.source_import.byte_exact",
|
||||
"repository.module.create",
|
||||
"source_map.evidence.write",
|
||||
"git.branch.patch.create",
|
||||
"commit_file.atomic",
|
||||
"git.push.patch_branch",
|
||||
"test.execute.allowlisted",
|
||||
"artifact.build.control_plane_patch",
|
||||
"evidence.write.task_runtime"
|
||||
],
|
||||
"allowed_paths": [
|
||||
"active-runtime/app/aios_autonomous_release_authorization/**",
|
||||
"active-runtime/app/aios_chatgpt_action_task_router/**",
|
||||
"active-runtime/app/aios_isolated_writer_api/**",
|
||||
"tests/test_integration_refresh_provisioner.py",
|
||||
"tests/test_isolated_writer_integration_merge.py",
|
||||
"tests/test_gitea_branch_read_projection.py",
|
||||
"tests/test_isolated_writer_token_policy_v2.py",
|
||||
"deploy/systemd/aios-integration-refresh-*.service",
|
||||
"docs/autonomous-release/**"
|
||||
],
|
||||
"allowed_runtime_reads": [
|
||||
"/usr/libexec/aios-autonomous-release-authorization/controller.py",
|
||||
"/usr/libexec/aios-chatgpt-action-task-router-continuous-20260726/action_task_router.py"
|
||||
],
|
||||
"approval_type": "SOURCE_MAPPING_AND_IMPORT",
|
||||
"canonical_paths": {
|
||||
"controller": "active-runtime/app/aios_autonomous_release_authorization/controller.py",
|
||||
"isolated_writer_api": "active-runtime/app/aios_isolated_writer_api/**",
|
||||
"router": "active-runtime/app/aios_chatgpt_action_task_router/action_task_router.py"
|
||||
},
|
||||
"denied_operations": [
|
||||
"secret.value.return",
|
||||
"secret.matching_line.return",
|
||||
"secret.context.return",
|
||||
"runtime.source.modify",
|
||||
"runtime.unit.modify",
|
||||
"arbitrary_path.read",
|
||||
"secret.read.by_agent",
|
||||
"secret.export",
|
||||
"main.write",
|
||||
"main.merge",
|
||||
"governance.approvals.direct_write",
|
||||
"integration.write",
|
||||
"tag.write",
|
||||
"tag.delete",
|
||||
"tag.rewrite",
|
||||
"force_push",
|
||||
"history.rewrite",
|
||||
"bare_ref_direct_write",
|
||||
"baseline.write",
|
||||
"registry.write",
|
||||
"production.deploy",
|
||||
"production.rollback"
|
||||
],
|
||||
"expires_at": "2026-07-27T22:36:27Z",
|
||||
"extends_approval_merge_oid": "45f3226662965c8d41ec1775e8c8857f2b986397",
|
||||
"finding_policy": {
|
||||
"controller_byte_exact_import_allowed": true,
|
||||
"controller_finding_status": "PASS",
|
||||
"controller_sanitized_source_creation_allowed": false,
|
||||
"finding_classification_by_automation": "PROHIBITED",
|
||||
"isolated_writer_api_module_create_allowed": true,
|
||||
"matching_source_lines_disclosed": false,
|
||||
"router_byte_exact_import_allowed": true,
|
||||
"router_finding_status": "PASS",
|
||||
"router_sanitized_source_creation_allowed": false,
|
||||
"scan_decision": "MAPPING_IMPORT_ONLY",
|
||||
"secret_content_disclosed": false
|
||||
},
|
||||
"implementation_base_oid": "875d41c3101093149414292067fd700dc214d4d3",
|
||||
"implementation_branch": "patch/integration-refresh-grant-provisioner-20260726",
|
||||
"issued_at": "2026-07-27T10:36:27Z",
|
||||
"repository": "aios/aios",
|
||||
"required_controls": {
|
||||
"controller_finding_must_be_resolved": false,
|
||||
"controller_import_blob_must_equal_runtime_blob": true,
|
||||
"controller_import_must_be_byte_exact": true,
|
||||
"controller_import_sha256_must_equal_runtime_sha256": true,
|
||||
"dual_human_approval_required": true,
|
||||
"global_gitops_writes_must_remain_disabled": true,
|
||||
"new_task_bound_grant_required": true,
|
||||
"old_implementation_token_auth_status": 401,
|
||||
"old_implementation_writer_revoked": true,
|
||||
"old_secret_revocation_must_be_verified": false,
|
||||
"router_finding_must_be_resolved": false,
|
||||
"router_import_blob_must_equal_runtime_blob": true,
|
||||
"router_import_must_be_byte_exact": true,
|
||||
"router_import_sha256_must_equal_runtime_sha256": true,
|
||||
"sanitized_source_behavioral_equivalence_required": false,
|
||||
"sanitized_source_secret_scan_must_pass": false,
|
||||
"true_positive_requires_rotation": false
|
||||
},
|
||||
"risk": "high",
|
||||
"runtime_source_evidence": {
|
||||
"controller": {
|
||||
"finding_count": 0,
|
||||
"findings": [],
|
||||
"git_blob_oid": "9c68be36e0b6a18260e3099e3d7b0dcab6d814aa",
|
||||
"group_gid": 0,
|
||||
"mode": "0o700",
|
||||
"mtime_ns": 1785043975531298047,
|
||||
"owner_uid": 0,
|
||||
"package_owner": "UNPACKAGED",
|
||||
"referencing_systemd_units": [
|
||||
"aios-autonomous-release-authorization-controller.service"
|
||||
],
|
||||
"resolved_path": "/usr/libexec/aios-autonomous-release-authorization/controller.py",
|
||||
"secret_content_returned": false,
|
||||
"secret_scan": "PASS",
|
||||
"sha256": "39b532c3058c4ae49ed691d7c66c3b7745a25111fa61039f388c8335fd262c50",
|
||||
"size": 24204,
|
||||
"utf8_python": true
|
||||
},
|
||||
"isolated_writer_api": {
|
||||
"canonical_source_path": "NOT_ESTABLISHED",
|
||||
"runtime_module_status": "ABSENT"
|
||||
},
|
||||
"router": {
|
||||
"finding_count": 0,
|
||||
"findings": [],
|
||||
"git_blob_oid": "ee0595ca61a0598f8cb3dc10865a255e21679b2a",
|
||||
"group_gid": 0,
|
||||
"mode": "0o644",
|
||||
"mtime_ns": 1785042222580725999,
|
||||
"owner_uid": 0,
|
||||
"package_owner": "UNPACKAGED",
|
||||
"referencing_systemd_units": [
|
||||
"aios-chatgpt-autonomous-release-route.service"
|
||||
],
|
||||
"resolved_path": "/usr/libexec/aios-chatgpt-action-task-router-continuous-20260726/action_task_router.py",
|
||||
"secret_content_returned": false,
|
||||
"secret_scan": "PASS",
|
||||
"sha256": "738fb7035a6b792115d6467b4d1e4ffd47a8c5d5646f7032ebc68ea587cbd554",
|
||||
"size": 25952,
|
||||
"utf8_python": true
|
||||
}
|
||||
},
|
||||
"schema": "aios.gitea-native-approval/v1",
|
||||
"task_id": "AIOS-INTEGRATION-REFRESH-PROVISIONER-IMPLEMENTATION-20260726",
|
||||
"writer_audience": "aios-integration-refresh-provisioner-development-writer"
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
{
|
||||
"actor_identity": "chatgpt-gitops-bot",
|
||||
"allowed_operations": [
|
||||
"runtime.source.read.exact",
|
||||
"runtime.source.hash",
|
||||
"runtime.unit_metadata.read",
|
||||
"package.ownership.read",
|
||||
"git.object.lookup.by_blob_oid",
|
||||
"secret.scan.local_source",
|
||||
"repository.source_import.byte_exact",
|
||||
"repository.module.create",
|
||||
"source_map.evidence.write",
|
||||
"git.branch.patch.create",
|
||||
"commit_file.atomic",
|
||||
"git.push.patch_branch",
|
||||
"test.execute.allowlisted",
|
||||
"artifact.build.control_plane_patch",
|
||||
"evidence.write.task_runtime"
|
||||
],
|
||||
"allowed_paths": [
|
||||
"active-runtime/app/aios_autonomous_release_authorization/**",
|
||||
"active-runtime/app/aios_chatgpt_action_task_router/**",
|
||||
"active-runtime/app/aios_isolated_writer_api/**",
|
||||
"tests/test_integration_refresh_provisioner.py",
|
||||
"tests/test_isolated_writer_integration_merge.py",
|
||||
"tests/test_gitea_branch_read_projection.py",
|
||||
"tests/test_isolated_writer_token_policy_v2.py",
|
||||
"deploy/systemd/aios-integration-refresh-*.service",
|
||||
"docs/autonomous-release/**"
|
||||
],
|
||||
"allowed_runtime_reads": [
|
||||
"/usr/libexec/aios-autonomous-release-authorization/controller.py",
|
||||
"/usr/libexec/aios-chatgpt-action-task-router-continuous-20260726/action_task_router.py"
|
||||
],
|
||||
"approval_type": "SOURCE_MAPPING_AND_IMPORT_EXECUTION_RENEWAL",
|
||||
"canonical_paths": {
|
||||
"controller": "active-runtime/app/aios_autonomous_release_authorization/controller.py",
|
||||
"isolated_writer_api": "active-runtime/app/aios_isolated_writer_api/**",
|
||||
"router": "active-runtime/app/aios_chatgpt_action_task_router/action_task_router.py"
|
||||
},
|
||||
"denied_operations": [
|
||||
"secret.value.return",
|
||||
"secret.matching_line.return",
|
||||
"secret.context.return",
|
||||
"runtime.source.modify",
|
||||
"runtime.unit.modify",
|
||||
"arbitrary_path.read",
|
||||
"secret.read.by_agent",
|
||||
"secret.export",
|
||||
"main.write",
|
||||
"main.merge",
|
||||
"governance.approvals.direct_write",
|
||||
"integration.write",
|
||||
"tag.write",
|
||||
"tag.delete",
|
||||
"tag.rewrite",
|
||||
"force_push",
|
||||
"history.rewrite",
|
||||
"bare_ref_direct_write",
|
||||
"baseline.write",
|
||||
"registry.write",
|
||||
"production.deploy",
|
||||
"production.rollback"
|
||||
],
|
||||
"expected_governance_base_oid": "9514062cf33e45941abe4a8c98377e0713b0b3f6",
|
||||
"expires_at": "2026-07-28T18:10:44Z",
|
||||
"extends_approval_merge_oid": "45f3226662965c8d41ec1775e8c8857f2b986397",
|
||||
"finding_policy": {
|
||||
"controller_byte_exact_import_allowed": true,
|
||||
"controller_finding_status": "PASS",
|
||||
"controller_sanitized_source_creation_allowed": false,
|
||||
"finding_classification_by_automation": "PROHIBITED",
|
||||
"isolated_writer_api_module_create_allowed": true,
|
||||
"matching_source_lines_disclosed": false,
|
||||
"router_byte_exact_import_allowed": true,
|
||||
"router_finding_status": "PASS",
|
||||
"router_sanitized_source_creation_allowed": false,
|
||||
"scan_decision": "MAPPING_IMPORT_ONLY",
|
||||
"secret_content_disclosed": false
|
||||
},
|
||||
"implementation_base_oid": "875d41c3101093149414292067fd700dc214d4d3",
|
||||
"implementation_branch": "patch/integration-refresh-grant-provisioner-20260726",
|
||||
"issued_at": "2026-07-28T06:10:44Z",
|
||||
"renews_source_mapping_approval_merge_oid": "9514062cf33e45941abe4a8c98377e0713b0b3f6",
|
||||
"renews_source_mapping_manifest_blob_oid": "00c5e90e529cb98c7750a5761b0a0d6ccf507174",
|
||||
"renews_source_mapping_manifest_sha256": "17af8ebc43673c0e46bb9ab1bf44497e8c147f1b4a9d7a8a404edd5c0abd6f21",
|
||||
"repository": "aios/aios",
|
||||
"required_controls": {
|
||||
"controller_finding_must_be_resolved": false,
|
||||
"controller_import_blob_must_equal_runtime_blob": true,
|
||||
"controller_import_must_be_byte_exact": true,
|
||||
"controller_import_sha256_must_equal_runtime_sha256": true,
|
||||
"dual_human_approval_required": true,
|
||||
"global_gitops_writes_must_remain_disabled": true,
|
||||
"new_task_bound_grant_required": true,
|
||||
"old_implementation_token_auth_status": 401,
|
||||
"old_implementation_writer_revoked": true,
|
||||
"old_secret_revocation_must_be_verified": false,
|
||||
"router_finding_must_be_resolved": false,
|
||||
"router_import_blob_must_equal_runtime_blob": true,
|
||||
"router_import_must_be_byte_exact": true,
|
||||
"router_import_sha256_must_equal_runtime_sha256": true,
|
||||
"sanitized_source_behavioral_equivalence_required": false,
|
||||
"sanitized_source_secret_scan_must_pass": false,
|
||||
"true_positive_requires_rotation": false
|
||||
},
|
||||
"risk": "high",
|
||||
"runtime_source_evidence": {
|
||||
"controller": {
|
||||
"finding_count": 0,
|
||||
"findings": [],
|
||||
"git_blob_oid": "9c68be36e0b6a18260e3099e3d7b0dcab6d814aa",
|
||||
"group_gid": 0,
|
||||
"mode": "0o700",
|
||||
"mtime_ns": 1785043975531298047,
|
||||
"owner_uid": 0,
|
||||
"package_owner": "UNPACKAGED",
|
||||
"referencing_systemd_units": [
|
||||
"aios-autonomous-release-authorization-controller.service"
|
||||
],
|
||||
"resolved_path": "/usr/libexec/aios-autonomous-release-authorization/controller.py",
|
||||
"secret_content_returned": false,
|
||||
"secret_scan": "PASS",
|
||||
"sha256": "39b532c3058c4ae49ed691d7c66c3b7745a25111fa61039f388c8335fd262c50",
|
||||
"size": 24204,
|
||||
"utf8_python": true
|
||||
},
|
||||
"isolated_writer_api": {
|
||||
"canonical_source_path": "NOT_ESTABLISHED",
|
||||
"runtime_module_status": "ABSENT"
|
||||
},
|
||||
"observed_at": "2026-07-28T05:53:01.253909Z",
|
||||
"router": {
|
||||
"finding_count": 0,
|
||||
"findings": [],
|
||||
"git_blob_oid": "ee0595ca61a0598f8cb3dc10865a255e21679b2a",
|
||||
"group_gid": 0,
|
||||
"mode": "0o644",
|
||||
"mtime_ns": 1785042222580725999,
|
||||
"owner_uid": 0,
|
||||
"package_owner": "UNPACKAGED",
|
||||
"referencing_systemd_units": [
|
||||
"aios-chatgpt-autonomous-release-route.service"
|
||||
],
|
||||
"resolved_path": "/usr/libexec/aios-chatgpt-action-task-router-continuous-20260726/action_task_router.py",
|
||||
"secret_content_returned": false,
|
||||
"secret_scan": "PASS",
|
||||
"sha256": "738fb7035a6b792115d6467b4d1e4ffd47a8c5d5646f7032ebc68ea587cbd554",
|
||||
"size": 25952,
|
||||
"utf8_python": true
|
||||
}
|
||||
},
|
||||
"runtime_source_read_projection_evidence_sha256": "23f03d9c6abe6f11e1acda5210b8708696e3b77caa0083b6bfaef30a44b2a35f",
|
||||
"schema": "aios.gitea-native-approval/v1",
|
||||
"task_id": "AIOS-INTEGRATION-REFRESH-PROVISIONER-IMPLEMENTATION-20260726",
|
||||
"writer_audience": "aios-integration-refresh-provisioner-development-writer"
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
[Unit]
|
||||
Description=AIOS exact two-parent governance merge broker (%i)
|
||||
After=network.target aios-gitea.service
|
||||
Requires=aios-gitea.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=root
|
||||
Group=root
|
||||
UMask=0077
|
||||
RuntimeDirectory=aios-exact-two-parent-merge/%i
|
||||
RuntimeDirectoryMode=0700
|
||||
LoadCredentialEncrypted=gitea-token:/run/aios-task-credential-broker/%i-merge.cred
|
||||
LoadCredentialEncrypted=audit-key:/run/aios-task-credential-broker/%i-audit.cred
|
||||
ExecStart=/usr/bin/python3 -m aios_single_owner_governance.exact_two_parent_merge_broker --grant /var/lib/aios-gitops/runtime/exact-two-parent-merge/%i/grant.json --grant-signature /var/lib/aios-gitops/runtime/exact-two-parent-merge/%i/grant.sig --allowed-signers /etc/aios-gitops/exact-two-parent-merge/allowed_signers --token %d/gitea-token --audit-key %d/audit-key --journal /var/lib/aios-gitops/runtime/exact-two-parent-merge/%i/journal/journal.sqlite3 --quarantine-root /run/aios-exact-two-parent-merge/%i
|
||||
Environment=AIOS_GITOPS_WRITES_ENABLED=false
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET
|
||||
ReadOnlyPaths=/var/lib/aios-gitops/runtime/exact-two-parent-merge/%i
|
||||
ReadOnlyPaths=/etc/aios-gitops/exact-two-parent-merge/allowed_signers
|
||||
ReadWritePaths=/var/lib/aios-gitops/runtime/exact-two-parent-merge/%i/journal
|
||||
ReadWritePaths=/data/aios-gitops/repo.git
|
||||
ReadWritePaths=/data/aios-gitops/runtime/gitea-bin/data/repositories/aios/aios.git
|
||||
ReadWritePaths=/run/aios-exact-two-parent-merge/%i
|
||||
IPAddressDeny=any
|
||||
IPAddressAllow=localhost
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
RestrictSUIDSGID=true
|
||||
@@ -0,0 +1,24 @@
|
||||
[Unit]
|
||||
Description=AIOS root task credential broker (%i)
|
||||
ConditionPathExists=/var/lib/aios-gitops/runtime/task-status-merge/%i/grant.json
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=root
|
||||
Group=root
|
||||
LoadCredentialEncrypted=audit-key:/run/aios-task-credential-broker/%i-audit.cred
|
||||
ExecStart=/usr/bin/python3 -m aios_single_owner_governance.task_credential_broker --grant /var/lib/aios-gitops/runtime/task-status-merge/%i/grant.json --grant-signature /var/lib/aios-gitops/runtime/task-status-merge/%i/grant.sig --allowed-signers /etc/aios-gitops/task-status-merge/allowed_signers --audit-key %d/audit-key --audit-dir /var/lib/aios-gitops/runtime/task-status-merge/%i/audit --request /var/lib/aios-gitops/runtime/task-status-merge/%i/credential-request.json
|
||||
Environment=AIOS_GITOPS_WRITES_ENABLED=false
|
||||
NoNewPrivileges=false
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ReadOnlyPaths=/data/aios-gitops/runtime/bin/gitea /data/aios-gitops/runtime/gitea-bin/custom/conf/app.ini /var/lib/aios-gitops/runtime/task-status-merge/%i/grant.json /var/lib/aios-gitops/runtime/task-status-merge/%i/grant.sig /var/lib/aios-gitops/runtime/task-status-merge/%i/credential-request.json /etc/aios-gitops/task-status-merge/allowed_signers /data/aios-gitops/runtime/gitea-bin/data/actions_artifacts /data/aios-gitops/runtime/gitea-bin/data/actions_log /data/aios-gitops/runtime/gitea-bin/data/attachments /data/aios-gitops/runtime/gitea-bin/data/avatars /data/aios-gitops/runtime/gitea-bin/data/gitea.db.bak.20260708133830 /data/aios-gitops/runtime/gitea-bin/data/gitea.db.bak-20260710-0105 /data/aios-gitops/runtime/gitea-bin/data/home /data/aios-gitops/runtime/gitea-bin/data/indexers /data/aios-gitops/runtime/gitea-bin/data/jwt /data/aios-gitops/runtime/gitea-bin/data/packages /data/aios-gitops/runtime/gitea-bin/data/queues /data/aios-gitops/runtime/gitea-bin/data/repo-archive /data/aios-gitops/runtime/gitea-bin/data/repo-avatars /data/aios-gitops/runtime/gitea-bin/data/repositories /data/aios-gitops/runtime/gitea-bin/data/tmp
|
||||
ReadWritePaths=/data/aios-gitops/runtime/gitea-bin/data /run/aios-task-credential-broker /var/lib/aios-gitops/runtime/task-status-merge/%i/audit
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET
|
||||
IPAddressDeny=any
|
||||
IPAddressAllow=localhost
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
RestrictSUIDSGID=true
|
||||
@@ -0,0 +1,23 @@
|
||||
[Unit]
|
||||
Description=AIOS task credential expiry and completion sweeper
|
||||
After=aios-gitea.service
|
||||
Requires=aios-gitea.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=root
|
||||
Group=root
|
||||
ExecStart=/usr/bin/python3 -m aios_single_owner_governance.task_credential_expiry_sweeper --runtime-root /var/lib/aios-gitops/runtime/task-status-merge --allowed-signers /etc/aios-gitops/task-status-merge/allowed_signers
|
||||
Environment=AIOS_GITOPS_WRITES_ENABLED=false
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ReadOnlyPaths=/etc/aios-gitops/task-status-merge/allowed_signers /data/aios-gitops/runtime/gitea-bin/data/actions_artifacts /data/aios-gitops/runtime/gitea-bin/data/actions_log /data/aios-gitops/runtime/gitea-bin/data/attachments /data/aios-gitops/runtime/gitea-bin/data/avatars /data/aios-gitops/runtime/gitea-bin/data/gitea.db.bak.20260708133830 /data/aios-gitops/runtime/gitea-bin/data/gitea.db.bak-20260710-0105 /data/aios-gitops/runtime/gitea-bin/data/home /data/aios-gitops/runtime/gitea-bin/data/indexers /data/aios-gitops/runtime/gitea-bin/data/jwt /data/aios-gitops/runtime/gitea-bin/data/packages /data/aios-gitops/runtime/gitea-bin/data/queues /data/aios-gitops/runtime/gitea-bin/data/repo-archive /data/aios-gitops/runtime/gitea-bin/data/repo-avatars /data/aios-gitops/runtime/gitea-bin/data/repositories /data/aios-gitops/runtime/gitea-bin/data/tmp
|
||||
ReadWritePaths=/data/aios-gitops/runtime/gitea-bin/data /run/aios-task-credential-broker /var/lib/aios-gitops/runtime/task-status-merge
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET
|
||||
IPAddressDeny=any
|
||||
IPAddressAllow=localhost
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
RestrictSUIDSGID=true
|
||||
@@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=Run AIOS task credential expiry sweeper every minute
|
||||
|
||||
[Timer]
|
||||
OnBootSec=1min
|
||||
OnUnitActiveSec=1min
|
||||
AccuracySec=5s
|
||||
Persistent=true
|
||||
Unit=aios-task-credential-expiry-sweeper.service
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -0,0 +1,26 @@
|
||||
[Unit]
|
||||
Description=AIOS task-scoped Gitea fast-forward-only merge broker (%i)
|
||||
After=network.target aios-gitea.service aios-task-status-broker@%i.service
|
||||
Requires=aios-gitea.service aios-task-status-broker@%i.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=aios-task-merge-bot
|
||||
Group=aios-task-merge-bot
|
||||
LoadCredentialEncrypted=gitea-token:/run/aios-task-credential-broker/%i-merge.cred
|
||||
LoadCredentialEncrypted=audit-key:/run/aios-task-credential-broker/%i-audit.cred
|
||||
ExecStart=/usr/bin/python3 -m aios_single_owner_governance.task_status_merge_broker --mode merge --grant /var/lib/aios-gitops/runtime/task-status-merge/%i/grant.json --grant-signature /var/lib/aios-gitops/runtime/task-status-merge/%i/grant.sig --allowed-signers /etc/aios-gitops/task-status-merge/allowed_signers --token %d/gitea-token --audit-key %d/audit-key --journal /var/lib/aios-gitops/runtime/task-status-merge/%i/merge-journal/journal.sqlite3
|
||||
Environment=AIOS_GITOPS_WRITES_ENABLED=false
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET
|
||||
ReadOnlyPaths=/var/lib/aios-gitops/runtime/task-status-merge/%i /etc/aios-gitops/task-status-merge/allowed_signers
|
||||
ReadWritePaths=/var/lib/aios-gitops/runtime/task-status-merge/%i/merge-journal
|
||||
IPAddressDeny=any
|
||||
IPAddressAllow=localhost
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
RestrictSUIDSGID=true
|
||||
@@ -0,0 +1,26 @@
|
||||
[Unit]
|
||||
Description=AIOS task-scoped Gitea status broker (%i)
|
||||
After=network.target aios-gitea.service
|
||||
Requires=aios-gitea.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=aios-task-status-bot
|
||||
Group=aios-task-status-bot
|
||||
LoadCredentialEncrypted=gitea-token:/run/aios-task-credential-broker/%i-status.cred
|
||||
LoadCredentialEncrypted=audit-key:/run/aios-task-credential-broker/%i-audit.cred
|
||||
ExecStart=/usr/bin/python3 -m aios_single_owner_governance.task_status_merge_broker --mode status --grant /var/lib/aios-gitops/runtime/task-status-merge/%i/grant.json --grant-signature /var/lib/aios-gitops/runtime/task-status-merge/%i/grant.sig --allowed-signers /etc/aios-gitops/task-status-merge/allowed_signers --evidence /var/lib/aios-gitops/runtime/task-status-merge/%i/status-evidence.json --token %d/gitea-token --audit-key %d/audit-key --journal /var/lib/aios-gitops/runtime/task-status-merge/%i/status-journal/journal.sqlite3
|
||||
Environment=AIOS_GITOPS_WRITES_ENABLED=false
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET
|
||||
ReadOnlyPaths=/var/lib/aios-gitops/runtime/task-status-merge/%i /etc/aios-gitops/task-status-merge/allowed_signers
|
||||
ReadWritePaths=/var/lib/aios-gitops/runtime/task-status-merge/%i/status-journal
|
||||
IPAddressDeny=any
|
||||
IPAddressAllow=localhost
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
RestrictSUIDSGID=true
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"evidence_type": "TWO_STAGE_OWNER_CAMPAIGN_V2_PHASE_A_CANDIDATE",
|
||||
"generated_at": "2026-07-30T02:35:51Z",
|
||||
"execution_status": "PARTIALLY_COMPLETED",
|
||||
"blocking_layer": "PHASE_A_OWNER_EXACT_HEAD_APPROVAL",
|
||||
"repository": "aios/aios",
|
||||
"governance_base_oid": "ebd74398743e36523b593fcc395b73945d4924c5",
|
||||
"governance_tree_oid": "ad1255a1127204fad1d1208d8f31dc94fa64e6e1",
|
||||
"authority_main_oid": "875d41c3101093149414292067fd700dc214d4d3",
|
||||
"schema_seed_commit_oid": "8da47545a507cb798d3d6ab30ff8552a2d0a5ecf",
|
||||
"schema_seed_tree_oid": "991e68577a469657c1c4996814b6634ce5ed752f",
|
||||
"candidate_source_commit_oid": "a637a9dbb6e0021cb7dcf0d6b0ca00dfe10dce38",
|
||||
"candidate_source_tree_oid": "bf6e0c16fcad7515465bec4aefdd2e77b1e9bef0",
|
||||
"candidate_diff_sha256": "c06d002db06da6cbf16f9c8321f1acbfd431f009b5127a13fd2cb5d63b278867",
|
||||
"candidate_files_sha256": "5061402d516c0e9413208ce64e2b52f77d224e6eec1f7b07c5b3a10b58df5eaf",
|
||||
"candidate_file_count": 44,
|
||||
"schema_document_path": "services/aios-single-owner-governance/task-scoped-governance-bootstrap-v1.schema.json",
|
||||
"schema_document_sha256": "6ec912a96c8bac5c0d3c0a017abc078cb9567ed54dcb20bb0eadb5926ac54fdd",
|
||||
"validator_policy_path": "services/aios-single-owner-governance/aios_single_owner_governance/task_scoped_bootstrap.py",
|
||||
"validator_policy_sha256": "de11123b5cd9ccdad4961f1d5d084a8c4a1b2270ce793652ab9c50bb95694eac",
|
||||
"two_stage_policy_schema_sha256": "6b4def7c1eef19c01ef73c43252eb0cd1a87c103e1047249a3cfcc75c7922bf6",
|
||||
"two_stage_release_schema_sha256": "82ee589dc87db6998ddf0779ce264194cadea0deca66088b0797378f6ce89ad3",
|
||||
"runtime_observer_sha256": "181e632192f1417a98c1efb35631cf80246759080b2e04243576abcdc7da22af",
|
||||
"journal_anchor_sha256": "18feb0792b95f1060aa696cc60943379695a246984aecf76098838f9ed37426e",
|
||||
"package_zip_sha256": "668239a549b4a2b2094fe9b5104b84703fe8ed75011fd8ed71ed3958a6dc0706",
|
||||
"candidate_bundle_sha256": "15711b225b9ef41cca0588d21b2b95d7a95ac155efc27fcb8772c435fcdab147",
|
||||
"anchor_install_wheel_sha256": "abbf616366eb3467d4e651bb5306e22ae5e931b613df5362a0892935c4a1c0c7",
|
||||
"tests": {
|
||||
"two_stage_campaign": {"passed": 67, "failed": 0, "skipped": 0, "collection_errors": 0},
|
||||
"single_owner_governance": {"passed": 105, "failed": 0, "subtests_passed": 17, "collection_errors": 0},
|
||||
"task_scoped_bootstrap": {"passed": 15, "failed": 0, "collection_errors": 0},
|
||||
"compileall": "PASS",
|
||||
"quarantine_fsck": "PASS",
|
||||
"systemd_verify": "PASS_WITH_UNRELATED_HOST_WARNINGS"
|
||||
},
|
||||
"anchor": {
|
||||
"service_status": "active",
|
||||
"anchor_uid": 988,
|
||||
"runner_uid": 986,
|
||||
"uids_distinct": true,
|
||||
"runner_database_write": "DENIED",
|
||||
"unauthorized_socket_peer": "DENIED",
|
||||
"runner_registration_action": "ANCHOR_REQUEST_INVALID",
|
||||
"journal_mode": "WAL",
|
||||
"synchronous": "FULL",
|
||||
"foreign_keys": "ON",
|
||||
"restart_open_reservation": "PASS",
|
||||
"selftest_open_reservation_cleanup": "ABORTED"
|
||||
},
|
||||
"phase_separation": {
|
||||
"required_owner_decisions": 2,
|
||||
"phase_a_authorization": "GITEA_EXACT_HEAD_REVIEW",
|
||||
"phase_b_authorization": "DETACHED_SSH_SIGNATURE",
|
||||
"phase_a_release_authority": false,
|
||||
"one_shot_authority": false
|
||||
},
|
||||
"isolation": {
|
||||
"AIOS_GITOPS_WRITES_ENABLED": false,
|
||||
"publisher_created": false,
|
||||
"writer_created": false,
|
||||
"credential_projection_created": false,
|
||||
"target_branch_created": false,
|
||||
"tag_created": false,
|
||||
"registry_modified": false,
|
||||
"baseline_modified": false,
|
||||
"production_modified": false
|
||||
},
|
||||
"governance": {
|
||||
"force_push_used": false,
|
||||
"tag_rewritten": false,
|
||||
"direct_main_push": false,
|
||||
"secrets_committed": false,
|
||||
"owner_approval_respected": true
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
1f4a83cf91e9aacb6f50bc4fb24d4f745a7a31fd1dd7f2d3d3ed0de05e38234f deploy/systemd/aios-campaign-anchor.service
|
||||
6b2525d31b8544488bce1544deb39a087cc2d830fb1b16fda26cde6ce430dbd2 deploy/systemd/aios-policy-activation-verification@.service
|
||||
fad52587775ff5f1c5cc16862c5fb1d910bb097c6b3330c00d75db4f7bef0bbd deploy/systemd/aios-policy-campaign-register@.service
|
||||
de11123b5cd9ccdad4961f1d5d084a8c4a1b2270ce793652ab9c50bb95694eac services/aios-single-owner-governance/aios_single_owner_governance/task_scoped_bootstrap.py
|
||||
6ec912a96c8bac5c0d3c0a017abc078cb9567ed54dcb20bb0eadb5926ac54fdd services/aios-single-owner-governance/task-scoped-governance-bootstrap-v1.schema.json
|
||||
5b6d65ae34b2c3bebc1bde8f38c6b0bb73269336d7d4ee5e3416f79c323134d6 services/aios-two-stage-owner-campaign/ANCHOR_REGISTRATION_CONTRACT.md
|
||||
8ba5278f96447bf4308813b49fcc433d0cc7a5019cd9e78aeb0d8ada5f00d53b services/aios-two-stage-owner-campaign/README.md
|
||||
7285527358bc8da1d4bc7ff70162dfc3a734086851b734daac89d6f9b330798d services/aios-two-stage-owner-campaign/SECURITY_DELTA_V2.md
|
||||
89f03fdf156d35807bf39f2f1dbf304c01e7e9ad64399276fb72c68d795eda65 services/aios-two-stage-owner-campaign/STEP_EXECUTOR_CONTRACT.md
|
||||
7453177223b62509e4854051a75d53f24d8d1f737986f309cc6ae7a2e721459f services/aios-two-stage-owner-campaign/TWO_STAGE_OWNER_INTERACTION_SPEC.md
|
||||
9308ee245a28a0f0ff623fbfc8e3961204b8e67194bffe536a9c3485ca126ac7 services/aios-two-stage-owner-campaign/examples/allowed_signers.example
|
||||
9ac7b7fdeb73a99d4f41bf3410ef16f67091d85f9082075287d388e6e5e37eed services/aios-two-stage-owner-campaign/examples/campaign-anchor.env.example
|
||||
926f0c0cfb7bd6db0ebab6bcb5a0530057b8d38ae1e0b436a69a12b44039462b services/aios-two-stage-owner-campaign/examples/executor-release.example.json
|
||||
8d46ec9279eb58c03337d41b63de2abdd9eb0d03f98f0806768dcd309d481ee9 services/aios-two-stage-owner-campaign/examples/policy-activation-verification.example.json
|
||||
b34d8895fe872ef27c62c759449757def698eb05157daff8badd0c86f64ee18c services/aios-two-stage-owner-campaign/examples/release-campaign-manifest.example.json
|
||||
a2992f493cf0547e1f4acd3fcd9b20e03c912d8b255e196f640d47e291ec9d8e services/aios-two-stage-owner-campaign/examples/runtime-observer-policy.example.json
|
||||
150c9a8debdcaa4dbb9fd51817ca8710ef805a32f2f96dc5490d0c4f77f2cb6f services/aios-two-stage-owner-campaign/examples/runtime-observer-release.example.json
|
||||
675b39d13223ad281a4d14a04e739639e470e4e2a2591b85264ba37f840f2b49 services/aios-two-stage-owner-campaign/payload/scripts/generate_diff_chunks.py
|
||||
5751764275e626c787dad17c2561edd86b2710ae0b3cfeafeec44d98126d9015 services/aios-two-stage-owner-campaign/payload/services/aios-autonomous-control-plane/aios_autonomous_control_plane/lineage_audit.py
|
||||
2b501bf32a22e1b10c7254d2268d11aa2424aac5756c52441557c25dbaaaa8d5 services/aios-two-stage-owner-campaign/payload/tests/autonomous_control_plane/test_lineage_audit_v3.py
|
||||
3f118bc7a4f2bdfdd47e2c86112e87f95162dba4d88c1579337bd1d442ec9832 services/aios-two-stage-owner-campaign/pyproject.toml
|
||||
6b4def7c1eef19c01ef73c43252eb0cd1a87c103e1047249a3cfcc75c7922bf6 services/aios-two-stage-owner-campaign/schemas/governance-policy-activation-verification-v1.schema.json
|
||||
82ee589dc87db6998ddf0779ce264194cadea0deca66088b0797378f6ce89ad3 services/aios-two-stage-owner-campaign/schemas/task-scoped-release-campaign-v1.schema.json
|
||||
d04543b5407dcefc0a41af1b8a65ebc2e62f779582ae959449f1ac37b305eecf services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/__init__.py
|
||||
935a1c1166b0c1ea35a82256345000bf2c73ded718d77773bc27a71ecce28f7d services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/__main__.py
|
||||
4e317e2fbf912662bfbf57d2f7fe1a9f7fd3d8c00f7ad09602d9e885ee4ef3cc services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/canonical.py
|
||||
863be28723553e5bf5df90eda49e96edf8417e765da7b41579f050b04336d480 services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/cli.py
|
||||
dae1e0ed0f841d928a1dd5094c64b1f39b3213301c1da747f45d032a18833c1e services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/errors.py
|
||||
baf8931566d2c2fb2985d1814b5ded89719bb4b0eba01e94cca9f3690baadeb2 services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/executor.py
|
||||
18feb0792b95f1060aa696cc60943379695a246984aecf76098838f9ed37426e services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/journal_anchor.py
|
||||
e24765d3c62f9934a921f099e43a002d349a6c9934438da82040e40238070c58 services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/orchestrator.py
|
||||
181e632192f1417a98c1efb35631cf80246759080b2e04243576abcdc7da22af services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/runtime_observer.py
|
||||
3f3ce81490b6f0893665ba296fc23be904ae0e5dcbcd3b602e25feb2571262b0 services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/signature.py
|
||||
c71d85d5df2ed0e5367de9aacd230f586e77f3b4c11cf52960c84bbdeec43eb7 services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/state_machine.py
|
||||
75ea8ed1c9b49b1ca3ce452fe229524ebd0ccd802945d3362dd668602ce1f98c services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/validator.py
|
||||
b1be7e1ed0fa0e16e041f77200d651cf46f6bb029c89d31f0b286d32c026cb66 services/aios-two-stage-owner-campaign/tests/test_canonical_snapshot.py
|
||||
0bb956135c90cf71fcb655c4d700c1f71c3442b9e9fddc376959890cdad823de services/aios-two-stage-owner-campaign/tests/test_executor_v2.py
|
||||
9bb8a9f1c3bd9aaf44db3dd2f5b810eb0cb650da0e5a5f5e2ea50be46f12664d services/aios-two-stage-owner-campaign/tests/test_lineage_payload.py
|
||||
681c14ddc0be90f94fd3c3d8a6768fe602ec8acd130ed00a8668505d2db57aa2 services/aios-two-stage-owner-campaign/tests/test_orchestrator_v2.py
|
||||
9edf44fe0129098c31d66f6b6f7ab252879b425e2844969d09e22c3c88e6b77d services/aios-two-stage-owner-campaign/tests/test_runtime_observer.py
|
||||
204e77c760380f8e29d10fc0afa4227497109d27493245c55a851de6ff69ac86 services/aios-two-stage-owner-campaign/tests/test_signature.py
|
||||
9bc5dfa237e6bf37ccc90bbc618a807892d323cdd41345717b296838b44cd22a services/aios-two-stage-owner-campaign/tests/test_state_machine.py
|
||||
a9ef9c11120f480e0cde3ccbf8a816705621506657d8ac10faf7e38fb54bf367 services/aios-two-stage-owner-campaign/tests/test_validator.py
|
||||
4ec67865474c6c7ebaa7d125490486e8644618b1666cbde597420e4e6fe8f1d4 tests/single_owner_governance/test_task_scoped_bootstrap.py
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"plan_id": "AIOS-BOOTSTRAP-RFC3339-FIX-ROLLBACK-V1",
|
||||
"schema_version": 1,
|
||||
"repository": "aios/aios",
|
||||
"approved_base_oid": "8814dff20222625b60454c613b61ccf15fa20e51",
|
||||
"governance_history_rewrite_allowed": false,
|
||||
"steps": [
|
||||
"stop_uncompleted_task_status_merge_units",
|
||||
"revoke_exact_task_tokens_and_verify_http_401",
|
||||
"remove_all_runtime_credential_projections",
|
||||
"remove_unactivated_runtime_units_and_non_root_task_accounts",
|
||||
"verify_governance_refs_and_production_refs_unchanged"
|
||||
],
|
||||
"internal_token_rotation": {
|
||||
"rollback_allowed_before_old_secret_revocation": true,
|
||||
"rollback_source": "root_only_encrypted_runtime_credential",
|
||||
"rollback_allowed_after_old_secret_revocation": false,
|
||||
"post_revocation_recovery": "issue_another_replacement_secret_under_new_governed_operation",
|
||||
"plaintext_secret_in_evidence_allowed": false
|
||||
},
|
||||
"required_closure": {
|
||||
"temporary_accounts_remaining": 0,
|
||||
"temporary_credentials_remaining": 0,
|
||||
"credential_projections_remaining": 0,
|
||||
"old_task_tokens_http_status": 401,
|
||||
"AIOS_GITOPS_WRITES_ENABLED": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"schema_id": "AIOS_BOOTSTRAP_RFC3339_FIX_TEST_SUMMARY",
|
||||
"schema_version": 1,
|
||||
"candidate_tree_before_manifest": "c72b13bfe0656de9deeff9652e3e13ada81b110e",
|
||||
"local_unittest": {
|
||||
"tests_passed": 19,
|
||||
"tests_failed": 0,
|
||||
"tests_skipped": 0
|
||||
},
|
||||
"remote_posix": {
|
||||
"pytest_tests_passed": 19,
|
||||
"pytest_subtests_passed": 16,
|
||||
"tests_failed": 0,
|
||||
"tests_skipped": 0,
|
||||
"compileall": "PASS",
|
||||
"json_schema": "PASS",
|
||||
"systemd_validation": "PASS"
|
||||
},
|
||||
"secret_scan": {
|
||||
"scanner": "gitleaks",
|
||||
"exact_changed_paths": "PASS",
|
||||
"exact_changed_path_findings": 0,
|
||||
"whole_tree_existing_findings": 10,
|
||||
"whole_tree_findings_claimed_as_new": false
|
||||
},
|
||||
"python_executable": {
|
||||
"path": "/usr/bin/python3.12",
|
||||
"sha256": "e1efa562c2cc2e35521a5c9c9b9939921001ff8ca9708a13ef15ace68cc2ccd7"
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"schema_id": "AIOS_GOVERNANCE_PR_SUPERSESSION_RECORD_V1",
|
||||
"recorded_at": "2026-08-01T08:48:25Z",
|
||||
"repository": "aios/aios",
|
||||
"pr_number": 39,
|
||||
"approved_base_oid": "9303a5d684e972bd15fe19f61f721075f4dda8b6",
|
||||
"approved_head_oid": "52a60a3c419a4a7f70a0af80913532c540ba0a4e",
|
||||
"approved_tree_oid": "37d64c19f06f75589201a8b6277b13356303f04e",
|
||||
"manifest_blob_oid": "f970cd1df80dbd4993cd0eb845608899e53b8315",
|
||||
"manifest_sha256": "d878dc5eaae51f263b5803d36025b37d994602c91d3925430afc1d82acc490cb",
|
||||
"approval_payload_sha256": "b36a25328cc8ab3810bfd967e87391fb128bc0a4ebf3389914084bceb067cd76",
|
||||
"premerge_block_receipt_path": "/var/lib/aios-gitops/runtime/task-status-merge/pr39-52a60a3c419a/audit/premerge-contract-block.json",
|
||||
"premerge_block_receipt_sha256": "5b9a94cf80f3e16e9f47f90d0c0622f0c001d2e582f4891c51d4c572c1f5fe8b",
|
||||
"marker": "SUPERSEDED_BY_GOVERNANCE_MERGE_CLOSURE_V2",
|
||||
"pr_head_mutated": false,
|
||||
"pr_merged": false,
|
||||
"review_deleted": false,
|
||||
"block_receipt_deleted": false
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"schema_id": "AIOS_EXACT_TWO_PARENT_MERGE_BOOTSTRAP_ROLLBACK_V1",
|
||||
"generated_at": "2026-08-01T08:48:25Z",
|
||||
"repository": "aios/aios",
|
||||
"governance_ref": "refs/heads/governance/approvals",
|
||||
"pre_activation_oid": "9303a5d684e972bd15fe19f61f721075f4dda8b6",
|
||||
"strategy": "DISABLE_RUNTIME_COMPONENT_WITHOUT_REWRITING_GOVERNANCE_HISTORY",
|
||||
"steps": [
|
||||
"stop_and_disable_only_the_exact_two_parent_merge_broker_instance",
|
||||
"revoke_its_task_scoped_merge_and_audit_credentials",
|
||||
"remove_only_its_task_scoped_runtime_projection",
|
||||
"verify_authority_gitea_and_api_governance_refs_remain_equal",
|
||||
"verify_aios_gitops_writes_enabled_remains_false",
|
||||
"preserve_the_approved_governance_commit_and_all_audit_receipts"
|
||||
],
|
||||
"forbidden": [
|
||||
"force_push",
|
||||
"governance_ref_rewind",
|
||||
"tag_rewrite",
|
||||
"main_write",
|
||||
"production_write",
|
||||
"review_deletion",
|
||||
"block_receipt_deletion"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"schema_id": "AIOS_EXACT_TWO_PARENT_MERGE_TEST_SUMMARY_V1",
|
||||
"generated_at": "2026-08-01T08:48:25Z",
|
||||
"platform": {
|
||||
"git": "2.43.0",
|
||||
"python": "3.12.3",
|
||||
"system": "Linux x86_64",
|
||||
"systemd": "255"
|
||||
},
|
||||
"results": {
|
||||
"compileall": "PASS",
|
||||
"exact_two_parent_and_package_closure_tests": {
|
||||
"passed": 14,
|
||||
"failed": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"task_scoped_release_control_plane_tests": {
|
||||
"passed": 377,
|
||||
"failed": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"json_schema": "PASS",
|
||||
"systemd_analyze_verify": "PASS",
|
||||
"systemd_analyze_verify_target": "aios-exact-two-parent-merge-broker@.service",
|
||||
"systemd_analyze_verify_log_sha256": "c5e82657bcf5d534577181d950a46973f0c47e282b05666e27047fcd61b8058d",
|
||||
"secret_scan": "PASS",
|
||||
"git_fsck_connectivity_only": "PASS",
|
||||
"fault_injection": "PASS",
|
||||
"deterministic_commit_oid_recalculation": "PASS",
|
||||
"deterministic_wheel_two_builds": "PASS",
|
||||
"deterministic_zip_two_builds": "PASS",
|
||||
"independent_zip_extract_and_manifest_validation": "PASS"
|
||||
},
|
||||
"package": {
|
||||
"revision": "V5R1",
|
||||
"zip_sha256": "e754e6fb9beeda33a17764e8dc515fbd3a2731a95af77e43af9aaacb38a262b6",
|
||||
"manifest_sha256": "b5ccbbf7d23191eb09610f30e8062b4febd2344e4525d86b5aeba77d27df0671",
|
||||
"manifest_entries": 150,
|
||||
"wheel_sha256": "91c7452965eb668b8f696d83ce43950bfe61805aed29a161610c9ad12d0d7b19",
|
||||
"deployment_definitions_sha256": "b1e7379b1d8cbc5c83b41fee278b2a85e115a6bd071f7e0670563659429b70c0"
|
||||
},
|
||||
"live_mutation_performed": false
|
||||
}
|
||||
+364
@@ -0,0 +1,364 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sqlite3
|
||||
import subprocess
|
||||
from typing import Any, Protocol
|
||||
|
||||
from .task_status_merge_broker import ALLOWED_CONTEXTS, ALLOWED_IDENTITIES, ContractDenied, GiteaClient, _time, canonical, read_runtime_secret
|
||||
|
||||
|
||||
OID = re.compile(r"^[0-9a-f]{40}$")
|
||||
SHA256 = re.compile(r"^[0-9a-f]{64}$")
|
||||
TZ = re.compile(r"^[+-](?:(?:0\d|1[0-3])[0-5]\d|1400)$")
|
||||
EMAIL = re.compile(r"^[^\s<>@]+@[^\s<>@]+$")
|
||||
AUTHORITY_REPO = "/data/aios-gitops/repo.git"
|
||||
GITEA_REPO = "/data/aios-gitops/runtime/gitea-bin/data/repositories/aios/aios.git"
|
||||
QUARANTINE_ROOT = "/run/aios-exact-two-parent-merge"
|
||||
SIGNER = "aios-exact-two-parent-merge-grant-signer"
|
||||
NAMESPACE = "aios-exact-two-parent-merge-grant-v1"
|
||||
|
||||
FIELDS = frozenset({
|
||||
"schema_id", "schema_version", "repository", "target_ref", "merge_mode", "operation_quota", "force",
|
||||
"pr_number", "expected_old_ref_oid", "approved_pr_head_oid", "approved_pr_tree_oid", "first_parent_oid",
|
||||
"second_parent_oid", "expected_merge_tree_oid", "expected_merge_commit_oid", "commit_message_utf8",
|
||||
"commit_message_sha256", "author_name", "author_email", "author_epoch", "author_timezone",
|
||||
"committer_name", "committer_email", "committer_epoch", "committer_timezone", "manifest_path",
|
||||
"manifest_blob_oid", "manifest_sha256", "payload_sha256", "status_identity", "merge_identity",
|
||||
"status_contexts", "status_evidence_sha256", "issued_at", "expires_at", "audit_key_ref",
|
||||
"grant_signer_identity", "grant_signature_namespace",
|
||||
})
|
||||
|
||||
|
||||
def _require_oid(value: Any, error: str = "OID_INVALID") -> str:
|
||||
if not isinstance(value, str) or not OID.fullmatch(value):
|
||||
raise ContractDenied(error)
|
||||
return value
|
||||
|
||||
|
||||
def _identity(name: Any, email: Any, epoch: Any, zone: Any, error: str) -> None:
|
||||
if (not isinstance(name, str) or not name or any(c in name for c in "\r\n\0<>")
|
||||
or not isinstance(email, str) or not EMAIL.fullmatch(email)
|
||||
or type(epoch) is not int or epoch < 0 or not isinstance(zone, str) or not TZ.fullmatch(zone)):
|
||||
raise ContractDenied(error)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ExactGrant:
|
||||
raw: dict[str, Any]
|
||||
issued_at: datetime
|
||||
expires_at: datetime
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, value: dict[str, Any], *, now: datetime | None = None) -> "ExactGrant":
|
||||
if not isinstance(value, dict) or set(value) != FIELDS:
|
||||
raise ContractDenied("GRANT_FIELDS_INVALID")
|
||||
fixed = {
|
||||
"schema_id": "AIOS_EXACT_TWO_PARENT_MERGE_COMMIT_V1", "schema_version": 1,
|
||||
"repository": "aios/aios", "target_ref": "refs/heads/governance/approvals",
|
||||
"merge_mode": "EXACT_TWO_PARENT_COMMIT", "operation_quota": 1, "force": False,
|
||||
"grant_signer_identity": SIGNER, "grant_signature_namespace": NAMESPACE,
|
||||
}
|
||||
if any(value[key] != expected for key, expected in fixed.items()):
|
||||
raise ContractDenied("FIXED_FIELD_INVALID")
|
||||
if type(value["pr_number"]) is not int or value["pr_number"] <= 0:
|
||||
raise ContractDenied("PR_INVALID")
|
||||
for key in ("expected_old_ref_oid", "approved_pr_head_oid", "approved_pr_tree_oid", "first_parent_oid",
|
||||
"second_parent_oid", "expected_merge_tree_oid", "expected_merge_commit_oid", "manifest_blob_oid"):
|
||||
_require_oid(value[key])
|
||||
if (value["first_parent_oid"] != value["expected_old_ref_oid"]
|
||||
or value["second_parent_oid"] != value["approved_pr_head_oid"]
|
||||
or value["expected_merge_tree_oid"] != value["approved_pr_tree_oid"]):
|
||||
raise ContractDenied("PARENT_OR_TREE_BINDING_INVALID")
|
||||
message = value["commit_message_utf8"]
|
||||
if (not isinstance(message, str) or not message or "\0" in message or "\r" in message
|
||||
or not message.endswith("\n") or message.endswith("\n\n")
|
||||
or hashlib.sha256(message.encode("utf-8")).hexdigest() != value["commit_message_sha256"]):
|
||||
raise ContractDenied("COMMIT_MESSAGE_INVALID")
|
||||
if not isinstance(value["commit_message_sha256"], str) or not SHA256.fullmatch(value["commit_message_sha256"]):
|
||||
raise ContractDenied("COMMIT_MESSAGE_SHA256_INVALID")
|
||||
_identity(value["author_name"], value["author_email"], value["author_epoch"], value["author_timezone"], "AUTHOR_INVALID")
|
||||
_identity(value["committer_name"], value["committer_email"], value["committer_epoch"], value["committer_timezone"], "COMMITTER_INVALID")
|
||||
if (not isinstance(value["manifest_path"], str) or not re.fullmatch(r"approvals/[A-Za-z0-9._/-]+\.json", value["manifest_path"])
|
||||
or ".." in value["manifest_path"].split("/")):
|
||||
raise ContractDenied("MANIFEST_PATH_INVALID")
|
||||
for key in ("manifest_sha256", "payload_sha256"):
|
||||
if not isinstance(value[key], str) or not SHA256.fullmatch(value[key]):
|
||||
raise ContractDenied("SHA256_INVALID")
|
||||
if value["status_identity"] not in ALLOWED_IDENTITIES or value["merge_identity"] not in ALLOWED_IDENTITIES:
|
||||
raise ContractDenied("IDENTITY_INVALID")
|
||||
if set(value["status_contexts"]) != ALLOWED_CONTEXTS or len(value["status_contexts"]) != 5:
|
||||
raise ContractDenied("STATUS_CONTEXTS_INVALID")
|
||||
evidence = value["status_evidence_sha256"]
|
||||
if not isinstance(evidence, dict) or set(evidence) != ALLOWED_CONTEXTS or any(not isinstance(x, str) or not SHA256.fullmatch(x) for x in evidence.values()):
|
||||
raise ContractDenied("STATUS_EVIDENCE_INVALID")
|
||||
if not isinstance(value["audit_key_ref"], str) or not re.fullmatch(r"systemd-credential://[A-Za-z0-9_.-]+", value["audit_key_ref"]):
|
||||
raise ContractDenied("AUDIT_KEY_REFERENCE_INVALID")
|
||||
issued, expires = _time(value["issued_at"]), _time(value["expires_at"])
|
||||
current = now or datetime.now(timezone.utc)
|
||||
if (expires - issued).total_seconds() <= 0 or (expires - issued).total_seconds() > 43200 or current >= expires:
|
||||
raise ContractDenied("GRANT_EXPIRED_OR_LIFETIME_INVALID")
|
||||
return cls(dict(value), issued, expires)
|
||||
|
||||
@property
|
||||
def digest(self) -> str:
|
||||
return hashlib.sha256(canonical(self.raw)).hexdigest()
|
||||
|
||||
def commit_payload(self) -> bytes:
|
||||
v = self.raw
|
||||
lines = [
|
||||
f"tree {v['expected_merge_tree_oid']}", f"parent {v['first_parent_oid']}", f"parent {v['second_parent_oid']}",
|
||||
f"author {v['author_name']} <{v['author_email']}> {v['author_epoch']} {v['author_timezone']}",
|
||||
f"committer {v['committer_name']} <{v['committer_email']}> {v['committer_epoch']} {v['committer_timezone']}", "",
|
||||
]
|
||||
return ("\n".join(lines) + v["commit_message_utf8"]).encode("utf-8")
|
||||
|
||||
def calculated_oid(self) -> str:
|
||||
payload = self.commit_payload()
|
||||
return hashlib.sha1(b"commit " + str(len(payload)).encode() + b"\0" + payload).hexdigest()
|
||||
|
||||
|
||||
def load_signed_grant(grant_path: Path, signature_path: Path, allowed_signers: Path) -> ExactGrant:
|
||||
if any(not path.is_absolute() or path.is_symlink() for path in (grant_path, signature_path, allowed_signers)):
|
||||
raise ContractDenied("GRANT_SIGNATURE_PATH_UNSAFE")
|
||||
raw = grant_path.read_bytes()
|
||||
try:
|
||||
value = json.loads(raw)
|
||||
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
|
||||
raise ContractDenied("GRANT_JSON_INVALID") from exc
|
||||
if raw != canonical(value) + b"\n":
|
||||
raise ContractDenied("GRANT_NOT_CANONICAL")
|
||||
grant = ExactGrant.from_dict(value)
|
||||
try:
|
||||
subprocess.run(["/usr/bin/ssh-keygen", "-Y", "verify", "-f", str(allowed_signers), "-I", SIGNER,
|
||||
"-n", NAMESPACE, "-s", str(signature_path)], input=raw, check=True,
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
||||
except (OSError, subprocess.CalledProcessError) as exc:
|
||||
raise ContractDenied("GRANT_SIGNATURE_INVALID") from exc
|
||||
return grant
|
||||
|
||||
|
||||
class API(Protocol):
|
||||
def get(self, path: str) -> Any: ...
|
||||
|
||||
|
||||
class Git(Protocol):
|
||||
def commit_tree(self, quarantine: Path, tree: str, parents: list[str], message: bytes, env: dict[str, str]) -> str: ...
|
||||
def verify_commit(self, repository: str, oid: str) -> dict[str, Any]: ...
|
||||
def tree(self, repository: str, oid: str) -> str: ...
|
||||
def blob(self, repository: str, commit: str, path: str) -> str: ...
|
||||
def blob_data(self, repository: str, oid: str) -> bytes: ...
|
||||
def import_object(self, repository: str, source: Path, oid: str) -> None: ...
|
||||
def fsck(self, repository: str, oid: str) -> None: ...
|
||||
def ref(self, repository: str, ref: str) -> str: ...
|
||||
def update_ref(self, repository: str, ref: str, new: str, old: str) -> None: ...
|
||||
|
||||
|
||||
class SubprocessGit:
|
||||
"""Production git adapter; every mutating command is object-only or CAS-only."""
|
||||
def __init__(self, *, gitea_repo: str = GITEA_REPO) -> None:
|
||||
self.gitea_repo = gitea_repo
|
||||
|
||||
def _run(self, arguments: list[str], *, payload: bytes | None = None, env: dict[str, str] | None = None) -> bytes:
|
||||
try:
|
||||
return subprocess.run(arguments, input=payload, check=True, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE, env=env).stdout
|
||||
except (OSError, subprocess.CalledProcessError) as exc:
|
||||
raise ContractDenied("GIT_OPERATION_FAILED") from exc
|
||||
|
||||
def commit_tree(self, quarantine: Path, tree: str, parents: list[str], message: bytes, env: dict[str, str]) -> str:
|
||||
self._run(["/usr/bin/git", "init", "--bare", str(quarantine)])
|
||||
environment = {
|
||||
"PATH": "/usr/bin:/bin",
|
||||
"LANG": "C",
|
||||
"LC_ALL": "C",
|
||||
"GIT_CONFIG_NOSYSTEM": "1",
|
||||
"GIT_CONFIG_GLOBAL": "/dev/null",
|
||||
}
|
||||
environment.update(env)
|
||||
environment["GIT_ALTERNATE_OBJECT_DIRECTORIES"] = f"{self.gitea_repo}/objects"
|
||||
oid = self._run(["/usr/bin/git", f"--git-dir={quarantine}", "commit-tree", tree,
|
||||
"-p", parents[0], "-p", parents[1]], payload=message, env=environment).decode().strip()
|
||||
return _require_oid(oid, "QUARANTINE_OBJECT_INVALID")
|
||||
|
||||
def _git(self, repository: str, *args: str) -> bytes:
|
||||
return self._run(["/usr/bin/git", f"--git-dir={repository}", *args])
|
||||
|
||||
def tree(self, repository: str, oid: str) -> str:
|
||||
return _require_oid(self._git(repository, "show", "-s", "--format=%T", oid).decode().strip(), "TREE_READBACK_INVALID")
|
||||
|
||||
def blob(self, repository: str, commit: str, path: str) -> str:
|
||||
fields = self._git(repository, "ls-tree", commit, "--", path).decode().split()
|
||||
if len(fields) < 3 or fields[1] != "blob": raise ContractDenied("MANIFEST_BLOB_BINDING_INVALID")
|
||||
return _require_oid(fields[2], "MANIFEST_BLOB_BINDING_INVALID")
|
||||
|
||||
def blob_data(self, repository: str, oid: str) -> bytes:
|
||||
return self._git(repository, "cat-file", "blob", oid)
|
||||
|
||||
def verify_commit(self, repository: str, oid: str) -> dict[str, Any]:
|
||||
raw = self._git(repository, "cat-file", "commit", oid).decode("utf-8")
|
||||
header, message = raw.split("\n\n", 1)
|
||||
rows = header.splitlines(); parents = [x.removeprefix("parent ") for x in rows if x.startswith("parent ")]
|
||||
tree = next((x.removeprefix("tree ") for x in rows if x.startswith("tree ")), "")
|
||||
author = next((x.removeprefix("author ") for x in rows if x.startswith("author ")), "")
|
||||
committer = next((x.removeprefix("committer ") for x in rows if x.startswith("committer ")), "")
|
||||
return {"tree": tree, "parents": parents, "author": author, "committer": committer, "message": message}
|
||||
|
||||
def import_object(self, repository: str, source: Path, oid: str) -> None:
|
||||
payload = self._git(str(source), "cat-file", "commit", oid)
|
||||
observed = self._run(["/usr/bin/git", f"--git-dir={repository}", "hash-object", "-t", "commit", "-w", "--stdin"], payload=payload).decode().strip()
|
||||
if observed != oid:
|
||||
raise ContractDenied("OBJECT_IMPORT_OID_MISMATCH")
|
||||
|
||||
def fsck(self, repository: str, oid: str) -> None:
|
||||
self._git(repository, "fsck", "--connectivity-only", oid)
|
||||
|
||||
def ref(self, repository: str, ref: str) -> str:
|
||||
return _require_oid(self._run(["/usr/bin/git", f"--git-dir={repository}", "rev-parse", "--verify", ref]).decode().strip(), "REF_READBACK_INVALID")
|
||||
|
||||
def update_ref(self, repository: str, ref: str, new: str, old: str) -> None:
|
||||
try:
|
||||
self._run(["/usr/bin/git", f"--git-dir={repository}", "update-ref", ref, new, old])
|
||||
except ContractDenied as exc:
|
||||
raise ContractDenied("GOVERNANCE_REF_COMPARE_AND_SWAP_FAILED") from exc
|
||||
|
||||
|
||||
class ExactTwoParentMergeBroker:
|
||||
def __init__(self, grant: ExactGrant, api: API, git: Git, journal_path: Path,
|
||||
audit_key: bytes, quarantine_root: Path | None = None):
|
||||
self.grant, self.api, self.git = grant, api, git
|
||||
self.journal_path = Path(journal_path)
|
||||
quarantine_base = Path(quarantine_root) if quarantine_root is not None else Path(QUARANTINE_ROOT)
|
||||
self.quarantine_root = quarantine_base / grant.digest
|
||||
if not audit_key: raise ContractDenied("AUDIT_KEY_EMPTY")
|
||||
self.audit_key = bytes(audit_key)
|
||||
|
||||
def _preflight(self, *, reconciling: bool) -> None:
|
||||
v = self.grant.raw
|
||||
user = self.api.get("/user")
|
||||
if not isinstance(user, dict) or user.get("login") != v["merge_identity"]:
|
||||
raise ContractDenied("MERGE_IDENTITY_INVALID")
|
||||
pr = self.api.get(f"/repos/aios/aios/pulls/{v['pr_number']}")
|
||||
reviews = self.api.get(f"/repos/aios/aios/pulls/{v['pr_number']}/reviews")
|
||||
if (not isinstance(pr, dict) or (pr.get("head") or {}).get("sha") != v["approved_pr_head_oid"]
|
||||
or (pr.get("base") or {}).get("ref") != "governance/approvals"
|
||||
or (not reconciling and (pr.get("state") != "open"
|
||||
or (pr.get("base") or {}).get("sha") != v["expected_old_ref_oid"]
|
||||
or pr.get("mergeable") is not True))):
|
||||
raise ContractDenied("PR_BINDING_INVALID")
|
||||
if self.git.tree(GITEA_REPO, v["approved_pr_head_oid"]) != v["approved_pr_tree_oid"]:
|
||||
raise ContractDenied("APPROVED_TREE_MISMATCH")
|
||||
blob = self.git.blob(GITEA_REPO, v["approved_pr_head_oid"], v["manifest_path"])
|
||||
raw_manifest = self.git.blob_data(GITEA_REPO, blob)
|
||||
try:
|
||||
manifest = json.loads(raw_manifest)
|
||||
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
|
||||
raise ContractDenied("MANIFEST_PAYLOAD_BINDING_INVALID") from exc
|
||||
if (not isinstance(manifest, dict) or "approval_payload" not in manifest
|
||||
or blob != v["manifest_blob_oid"] or hashlib.sha256(raw_manifest).hexdigest() != v["manifest_sha256"]
|
||||
or hashlib.sha256(canonical(manifest["approval_payload"])).hexdigest() != v["payload_sha256"]):
|
||||
raise ContractDenied("MANIFEST_PAYLOAD_BINDING_INVALID")
|
||||
owners = [r for r in reviews if isinstance(r, dict) and (r.get("user") or {}).get("id") == 2 and (r.get("user") or {}).get("login") == "owner"] if isinstance(reviews, list) else []
|
||||
if not owners or any(k not in max(owners, key=lambda r: int(r.get("id") or -1)) for k in ("state", "commit_id")):
|
||||
raise ContractDenied("OWNER_APPROVAL_INVALID")
|
||||
review = max(owners, key=lambda r: int(r.get("id") or -1))
|
||||
if review.get("state") != "APPROVED" or review.get("commit_id") != v["approved_pr_head_oid"] or review.get("official") is not True or review.get("stale") is not False or review.get("dismissed") is not False:
|
||||
raise ContractDenied("OWNER_APPROVAL_INVALID")
|
||||
try: submitted = _time(str(review.get("submitted_at") or ""))
|
||||
except ContractDenied as exc: raise ContractDenied("OWNER_APPROVAL_INVALID") from exc
|
||||
if submitted < self.grant.issued_at or submitted >= self.grant.expires_at: raise ContractDenied("OWNER_APPROVAL_OUTSIDE_WINDOW")
|
||||
rows = self.api.get(f"/repos/aios/aios/commits/{v['approved_pr_head_oid']}/statuses")
|
||||
latest: dict[str, dict[str, Any]] = {}
|
||||
for row in rows if isinstance(rows, list) else []:
|
||||
if not isinstance(row, dict) or row.get("context") not in ALLOWED_CONTEXTS: continue
|
||||
context = row["context"]; key = (str(row.get("created_at") or ""), int(row.get("id") or 0))
|
||||
if context not in latest or key > (str(latest[context].get("created_at") or ""), int(latest[context].get("id") or 0)): latest[context] = row
|
||||
if set(latest) != ALLOWED_CONTEXTS or any(r.get("status", r.get("state")) != "success" or r.get("sha") != v["approved_pr_head_oid"] or (r.get("creator") or {}).get("login") != v["status_identity"] or not str(r.get("description") or "").endswith(v["status_evidence_sha256"][c]) for c, r in latest.items()):
|
||||
raise ContractDenied("STATUS_BINDING_INVALID")
|
||||
|
||||
def _journal(self) -> sqlite3.Connection:
|
||||
db = sqlite3.connect(self.journal_path, isolation_level=None)
|
||||
if str(db.execute("PRAGMA journal_mode=WAL").fetchone()[0]).lower() != "wal": raise ContractDenied("JOURNAL_MODE_INVALID")
|
||||
db.execute("PRAGMA synchronous=FULL")
|
||||
if int(db.execute("PRAGMA synchronous").fetchone()[0]) != 2: raise ContractDenied("JOURNAL_SYNCHRONOUS_INVALID")
|
||||
db.execute("CREATE TABLE IF NOT EXISTS exact_merge(request_sha256 TEXT PRIMARY KEY, state TEXT NOT NULL, receipt BLOB, previous_hash TEXT, audit_hash TEXT)")
|
||||
return db
|
||||
|
||||
def execute(self) -> dict[str, Any]:
|
||||
db = self._journal()
|
||||
try:
|
||||
row = db.execute("SELECT request_sha256,state,receipt FROM exact_merge").fetchone()
|
||||
if row:
|
||||
if row[0] != self.grant.digest: raise ContractDenied("JOURNAL_REQUEST_MISMATCH")
|
||||
if row[1] == "COMMITTED": return json.loads(row[2])
|
||||
else:
|
||||
db.execute("BEGIN IMMEDIATE"); db.execute("INSERT INTO exact_merge(request_sha256,state) VALUES(?, 'RESERVED')", (self.grant.digest,)); db.execute("COMMIT")
|
||||
payload, expected = self.grant.commit_payload(), self.grant.raw["expected_merge_commit_oid"]
|
||||
old = self.grant.raw["expected_old_ref_oid"]
|
||||
observed = {repo: self.git.ref(repo, self.grant.raw["target_ref"]) for repo in (GITEA_REPO, AUTHORITY_REPO)}
|
||||
if any(value not in {old, expected} for value in observed.values()):
|
||||
raise ContractDenied("GOVERNANCE_REF_COMPARE_AND_SWAP_FAILED")
|
||||
self._preflight(reconciling=expected in observed.values())
|
||||
if self.grant.calculated_oid() != expected:
|
||||
raise ContractDenied("EXPECTED_COMMIT_OID_MISMATCH")
|
||||
env = {"GIT_AUTHOR_NAME": self.grant.raw["author_name"], "GIT_AUTHOR_EMAIL": self.grant.raw["author_email"], "GIT_AUTHOR_DATE": f"{self.grant.raw['author_epoch']} {self.grant.raw['author_timezone']}", "GIT_COMMITTER_NAME": self.grant.raw["committer_name"], "GIT_COMMITTER_EMAIL": self.grant.raw["committer_email"], "GIT_COMMITTER_DATE": f"{self.grant.raw['committer_epoch']} {self.grant.raw['committer_timezone']}"}
|
||||
a = self.git.commit_tree(self.quarantine_root / "a", self.grant.raw["expected_merge_tree_oid"], [old, self.grant.raw["approved_pr_head_oid"]], self.grant.raw["commit_message_utf8"].encode(), env)
|
||||
b = self.git.commit_tree(self.quarantine_root / "b", self.grant.raw["expected_merge_tree_oid"], [old, self.grant.raw["approved_pr_head_oid"]], self.grant.raw["commit_message_utf8"].encode(), env)
|
||||
if a != expected or b != expected or a != b:
|
||||
raise ContractDenied("DETERMINISTIC_COMMIT_MISMATCH")
|
||||
expected_meta = {"tree": self.grant.raw["expected_merge_tree_oid"], "parents": [old, self.grant.raw["approved_pr_head_oid"]], "author": f"{self.grant.raw['author_name']} <{self.grant.raw['author_email']}> {self.grant.raw['author_epoch']} {self.grant.raw['author_timezone']}", "committer": f"{self.grant.raw['committer_name']} <{self.grant.raw['committer_email']}> {self.grant.raw['committer_epoch']} {self.grant.raw['committer_timezone']}", "message": self.grant.raw["commit_message_utf8"]}
|
||||
if self.git.verify_commit(str(self.quarantine_root / "a"), a) != expected_meta or self.git.verify_commit(str(self.quarantine_root / "b"), b) != expected_meta: raise ContractDenied("COMMIT_VERIFICATION_INVALID")
|
||||
for repo in (GITEA_REPO, AUTHORITY_REPO):
|
||||
self.git.import_object(repo, self.quarantine_root / "a", expected)
|
||||
if self.git.verify_commit(repo, expected) != expected_meta: raise ContractDenied("IMPORTED_COMMIT_VERIFICATION_INVALID")
|
||||
self.git.fsck(repo, expected)
|
||||
new = expected
|
||||
observed = {repo: self.git.ref(repo, self.grant.raw["target_ref"]) for repo in (GITEA_REPO, AUTHORITY_REPO)}
|
||||
if any(value not in {old, new} for value in observed.values()):
|
||||
raise ContractDenied("GOVERNANCE_REF_COMPARE_AND_SWAP_FAILED")
|
||||
for repo, value in observed.items():
|
||||
if value == old:
|
||||
self.git.update_ref(repo, self.grant.raw["target_ref"], new, old)
|
||||
final = {repo: self.git.ref(repo, self.grant.raw["target_ref"]) for repo in (GITEA_REPO, AUTHORITY_REPO)}
|
||||
if set(final.values()) != {new}:
|
||||
raise ContractDenied("CAS_READBACK_INVALID")
|
||||
branch = self.api.get("/repos/aios/aios/branches/governance%2Fapprovals")
|
||||
if not isinstance(branch, dict) or (branch.get("commit") or {}).get("id") != new: raise ContractDenied("GITEA_BRANCH_READBACK_INVALID")
|
||||
receipt = {"schema_id":"AIOS_EXACT_TWO_PARENT_MERGE_CLOSURE_V1","schema_version":1,"status":"PASS","repository":"aios/aios","target_ref":self.grant.raw["target_ref"],"merge_mode":self.grant.raw["merge_mode"],"force":False,"request_sha256":self.grant.digest,"expected_old_ref_oid":old,"approved_pr_head_oid":self.grant.raw["approved_pr_head_oid"],"approved_pr_tree_oid":self.grant.raw["approved_pr_tree_oid"],"merge_commit_oid":expected,"parent_oids":[old,self.grant.raw["approved_pr_head_oid"]],"tree_oid":self.grant.raw["approved_pr_tree_oid"],"commit_message_sha256":self.grant.raw["commit_message_sha256"],"owner_review_exact_head":True,"required_statuses_exact_head":True,"quarantine_reproducibility":"PASS","authority_fsck":"PASS","gitea_fsck":"PASS","authority_ref_oid":final[AUTHORITY_REPO],"gitea_ref_oid":final[GITEA_REPO],"gitea_api_ref_oid":(branch.get("commit") or {}).get("id")}
|
||||
prior = db.execute("SELECT audit_hash FROM exact_merge WHERE audit_hash IS NOT NULL ORDER BY rowid DESC LIMIT 1").fetchone(); previous = prior[0] if prior else "0" * 64
|
||||
receipt["audit_chain_hash"] = hashlib.sha256(previous.encode() + canonical(receipt)).hexdigest()
|
||||
receipt["audit_hmac_signature"] = hmac.new(self.audit_key, canonical(receipt), hashlib.sha256).hexdigest()
|
||||
db.execute("UPDATE exact_merge SET state='COMMITTED',receipt=?,previous_hash=?,audit_hash=? WHERE request_sha256=?", (canonical(receipt), previous, receipt["audit_chain_hash"], self.grant.digest))
|
||||
return receipt
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(prog="aios-exact-two-parent-merge-broker")
|
||||
parser.add_argument("--grant", required=True, type=Path)
|
||||
parser.add_argument("--grant-signature", required=True, type=Path)
|
||||
parser.add_argument("--allowed-signers", required=True, type=Path)
|
||||
parser.add_argument("--token", required=True, type=Path)
|
||||
parser.add_argument("--audit-key", required=True, type=Path)
|
||||
parser.add_argument("--journal", required=True, type=Path)
|
||||
parser.add_argument("--quarantine-root", required=True, type=Path)
|
||||
args = parser.parse_args(argv)
|
||||
if any(not path.is_absolute() for path in (args.grant, args.grant_signature, args.allowed_signers, args.token, args.audit_key, args.journal, args.quarantine_root)):
|
||||
raise ContractDenied("CLI_PATH_NOT_ABSOLUTE")
|
||||
grant = load_signed_grant(args.grant, args.grant_signature, args.allowed_signers)
|
||||
result = ExactTwoParentMergeBroker(grant, GiteaClient(args.token), SubprocessGit(), args.journal,
|
||||
read_runtime_secret(args.audit_key), args.quarantine_root).execute()
|
||||
print(canonical(result).decode("utf-8"))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sqlite3
|
||||
import subprocess
|
||||
from datetime import datetime, timezone
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
from .task_status_merge_broker import ContractDenied, TaskGrant, canonical, load_signed_task_grant, read_runtime_secret
|
||||
|
||||
|
||||
GITEA_BIN = Path("/data/aios-gitops/runtime/bin/gitea")
|
||||
GITEA_CONFIG = Path("/data/aios-gitops/runtime/gitea-bin/custom/conf/app.ini")
|
||||
GITEA_DB = Path("/data/aios-gitops/runtime/gitea-bin/data/gitea.db")
|
||||
RUNUSER = Path("/usr/sbin/runuser")
|
||||
SYSTEMD_CREDS = Path("/usr/bin/systemd-creds")
|
||||
GIT = Path("/usr/bin/git")
|
||||
GITEA_REPOSITORY = Path("/data/aios-gitops/runtime/gitea-bin/data/repositories/aios/aios.git")
|
||||
|
||||
|
||||
def _git(*arguments: str, input_bytes: bytes | None = None) -> bytes:
|
||||
return subprocess.run(
|
||||
[str(GIT), f"--git-dir={GITEA_REPOSITORY}", *arguments], input=input_bytes,
|
||||
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
).stdout
|
||||
|
||||
|
||||
def _verify_local_governance_binding(grant: TaskGrant) -> None:
|
||||
"""Verify the exact open PR and latest Owner review without issuing a token."""
|
||||
connection = sqlite3.connect(f"file:{GITEA_DB}?mode=ro", uri=True)
|
||||
connection.row_factory = sqlite3.Row
|
||||
try:
|
||||
pr = connection.execute(
|
||||
"SELECT pr.head_branch,pr.base_branch,pr.merge_base,pr.has_merged,i.is_closed "
|
||||
"FROM pull_request pr JOIN issue i ON i.id=pr.issue_id JOIN repository repo ON repo.id=i.repo_id "
|
||||
"WHERE repo.owner_name='aios' AND repo.lower_name='aios' AND pr.[index]=?",
|
||||
(grant.raw["pr_number"],),
|
||||
).fetchone()
|
||||
review = connection.execute(
|
||||
"SELECT r.type,r.commit_id,r.official,r.stale,r.dismissed,r.created_unix,u.id,u.lower_name "
|
||||
"FROM review r JOIN user u ON u.id=r.reviewer_id JOIN issue i ON i.id=r.issue_id "
|
||||
"JOIN repository repo ON repo.id=i.repo_id "
|
||||
"WHERE repo.owner_name='aios' AND repo.lower_name='aios' AND i.[index]=? "
|
||||
"AND u.id=2 AND u.lower_name='owner' ORDER BY r.id DESC LIMIT 1",
|
||||
(grant.raw["pr_number"],),
|
||||
).fetchone()
|
||||
finally:
|
||||
connection.close()
|
||||
if pr is None or pr["base_branch"] != "governance/approvals" or pr["merge_base"] != grant.raw["base_oid"] or pr["has_merged"] != 0 or pr["is_closed"] != 0:
|
||||
raise ContractDenied("LOCAL_PR_BINDING_INVALID")
|
||||
head = _git("rev-parse", f"refs/heads/{pr['head_branch']}").decode().strip()
|
||||
base = _git("rev-parse", "refs/heads/governance/approvals").decode().strip()
|
||||
if head != grant.raw["target_commit"] or base != grant.raw["base_oid"]:
|
||||
raise ContractDenied("LOCAL_REF_BINDING_INVALID")
|
||||
if review is None or review["type"] != 1 or review["commit_id"] != head or review["official"] != 1 or review["stale"] != 0 or review["dismissed"] != 0:
|
||||
raise ContractDenied("LOCAL_OWNER_APPROVAL_MISSING")
|
||||
submitted = datetime.fromtimestamp(review["created_unix"], tz=timezone.utc)
|
||||
if submitted < grant.issued_at or submitted >= grant.expires_at:
|
||||
raise ContractDenied("LOCAL_OWNER_APPROVAL_EXPIRED")
|
||||
tree = _git("show", "-s", "--format=%T", head).decode().strip()
|
||||
if tree != grant.raw["approved_tree_oid"]:
|
||||
raise ContractDenied("APPROVED_TREE_MISMATCH")
|
||||
entry = _git("ls-tree", head, "--", grant.raw["manifest_path"]).decode().strip().split()
|
||||
if len(entry) < 3 or entry[1] != "blob" or entry[2] != grant.raw["manifest_blob_oid"]:
|
||||
raise ContractDenied("MANIFEST_BLOB_BINDING_INVALID")
|
||||
manifest = _git("cat-file", "blob", grant.raw["manifest_blob_oid"])
|
||||
if hashlib.sha256(manifest).hexdigest() != grant.raw["manifest_sha256"]:
|
||||
raise ContractDenied("MANIFEST_SHA256_MISMATCH")
|
||||
|
||||
|
||||
def _safe_absolute(path: Path, expected_parent: Path) -> None:
|
||||
if not path.is_absolute() or path.is_symlink() or path.parent != expected_parent:
|
||||
raise ContractDenied("CREDENTIAL_PATH_UNSAFE")
|
||||
|
||||
|
||||
class TaskCredentialBroker:
|
||||
def __init__(self, grant: TaskGrant, audit_dir: Path, audit_key: bytes):
|
||||
self.grant = grant
|
||||
self.audit_key = bytes(audit_key)
|
||||
if not self.audit_key:
|
||||
raise ContractDenied("CREDENTIAL_AUDIT_KEY_EMPTY")
|
||||
self.audit_dir = Path(audit_dir)
|
||||
if not self.audit_dir.is_absolute() or self.audit_dir.is_symlink():
|
||||
raise ContractDenied("AUDIT_DIR_UNSAFE")
|
||||
|
||||
def provision(self, *, identity: str, token_name: str, encrypted_path: Path) -> dict:
|
||||
if os.geteuid() != 0:
|
||||
raise ContractDenied("ROOT_BROKER_REQUIRED")
|
||||
expected = self.grant.raw["status_identity"] if token_name.endswith("-status") else self.grant.raw["merge_identity"]
|
||||
if identity != expected or identity not in {"codex", "gpt"}:
|
||||
raise ContractDenied("IDENTITY_BINDING_INVALID")
|
||||
prefix = f"pr{self.grant.raw['pr_number']}-{self.grant.raw['target_commit'][:12]}"
|
||||
if token_name not in {f"{prefix}-status", f"{prefix}-merge"}:
|
||||
raise ContractDenied("TOKEN_NAME_INVALID")
|
||||
_safe_absolute(encrypted_path, Path("/run/aios-task-credential-broker"))
|
||||
if encrypted_path.exists():
|
||||
raise ContractDenied("CREDENTIAL_PATH_EXISTS")
|
||||
_verify_local_governance_binding(self.grant)
|
||||
command = [str(RUNUSER), "-u", "aiman", "--", str(GITEA_BIN), "admin", "user", "generate-access-token", "--config", str(GITEA_CONFIG), "--username", identity, "--token-name", token_name, "--scopes", "write:repository", "--raw"]
|
||||
issued = subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.strip()
|
||||
if len(issued) < 32 or b"\n" in issued or b"\x00" in issued:
|
||||
raise ContractDenied("ISSUED_TOKEN_INVALID")
|
||||
subprocess.run([str(SYSTEMD_CREDS), "encrypt", "--name=gitea-token", "-", str(encrypted_path)], input=issued, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
||||
os.chmod(encrypted_path, 0o400)
|
||||
connection = sqlite3.connect(f"file:{GITEA_DB}?mode=ro", uri=True)
|
||||
try:
|
||||
rows = connection.execute("SELECT t.id,u.id FROM access_token t JOIN user u ON u.id=t.uid WHERE u.lower_name=? AND t.name=? AND t.scope='write:repository'", (identity, token_name)).fetchall()
|
||||
finally:
|
||||
connection.close()
|
||||
if len(rows) != 1:
|
||||
raise ContractDenied("TOKEN_DATABASE_BINDING_MISSING")
|
||||
row = rows[0]
|
||||
receipt = {"status": "PASS", "operation": "credential_provision", "identity": identity, "token_name": token_name, "token_id": int(row[0]), "token_uid": int(row[1]), "grant_sha256": self.grant.digest, "credential_ref": f"systemd-credential-encrypted://{encrypted_path.name}"}
|
||||
return self._audit(receipt)
|
||||
|
||||
def revoke(self, *, receipt: dict, encrypted_path: Path) -> dict:
|
||||
if os.geteuid() != 0:
|
||||
raise ContractDenied("ROOT_BROKER_REQUIRED")
|
||||
_safe_absolute(encrypted_path, Path("/run/aios-task-credential-broker"))
|
||||
self.validate_provision_receipt(receipt, encrypted_path)
|
||||
decrypted = subprocess.run([str(SYSTEMD_CREDS), "decrypt", str(encrypted_path), "-"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.strip()
|
||||
if len(decrypted) < 32 or b"\n" in decrypted or b"\x00" in decrypted:
|
||||
raise ContractDenied("PROJECTED_TOKEN_INVALID")
|
||||
token_header = {"Authorization": "token " + decrypted.decode("utf-8")}
|
||||
preflight = urllib.request.Request("http://127.0.0.1:3000/api/v1/repos/aios/aios", headers=token_header)
|
||||
try:
|
||||
with urllib.request.urlopen(preflight, timeout=10) as response:
|
||||
if response.status != 200:
|
||||
raise ContractDenied("TOKEN_PREFLIGHT_STATUS_INVALID")
|
||||
except urllib.error.HTTPError as exc:
|
||||
raise ContractDenied(f"TOKEN_PREFLIGHT_FAILED:{exc.code}") from exc
|
||||
connection = sqlite3.connect(GITEA_DB)
|
||||
try:
|
||||
connection.execute("BEGIN IMMEDIATE")
|
||||
cursor = connection.execute(
|
||||
"DELETE FROM access_token WHERE id=? AND uid=? AND name=? AND scope='write:repository'",
|
||||
(receipt["token_id"], receipt["token_uid"], receipt["token_name"]),
|
||||
)
|
||||
if cursor.rowcount != 1:
|
||||
raise ContractDenied("TOKEN_REVOCATION_MISMATCH")
|
||||
connection.commit()
|
||||
except Exception:
|
||||
connection.rollback()
|
||||
raise
|
||||
finally:
|
||||
connection.close()
|
||||
connection = sqlite3.connect(f"file:{GITEA_DB}?mode=ro", uri=True)
|
||||
try:
|
||||
remaining = connection.execute("SELECT COUNT(*) FROM access_token WHERE id=?", (receipt["token_id"],)).fetchone()[0]
|
||||
finally:
|
||||
connection.close()
|
||||
if remaining != 0:
|
||||
raise ContractDenied("TOKEN_REVOCATION_READBACK_MISMATCH")
|
||||
request = urllib.request.Request("http://127.0.0.1:3000/api/v1/repos/aios/aios", headers=token_header)
|
||||
try:
|
||||
urllib.request.urlopen(request, timeout=10)
|
||||
raise ContractDenied("REVOKED_TOKEN_STILL_ACCEPTED")
|
||||
except urllib.error.HTTPError as exc:
|
||||
if exc.code != 401:
|
||||
raise ContractDenied("REVOCATION_HTTP_STATUS_INVALID") from exc
|
||||
encrypted_path.unlink(missing_ok=True)
|
||||
result = {"status": "PASS", "operation": "credential_revoke", "identity": receipt["identity"], "token_name": receipt["token_name"], "token_id": receipt["token_id"], "grant_sha256": self.grant.digest, "token_rows_remaining": 0, "credential_projection_remaining": False, "old_token_http_status": 401}
|
||||
return self._audit(result)
|
||||
|
||||
def validate_provision_receipt(self, receipt: dict, encrypted_path: Path) -> None:
|
||||
required = {"status", "operation", "identity", "token_name", "token_id", "token_uid", "grant_sha256", "credential_ref", "audit_sha256", "audit_hmac_signature"}
|
||||
expected_credential_ref = f"systemd-credential-encrypted://{encrypted_path.name}"
|
||||
unsigned_receipt = {key: value for key, value in receipt.items() if key not in {"audit_sha256", "audit_hmac_signature"}}
|
||||
expected_name_prefix = f"pr{self.grant.raw['pr_number']}-{self.grant.raw['target_commit'][:12]}"
|
||||
if (set(receipt) != required or receipt["status"] != "PASS" or receipt["operation"] != "credential_provision"
|
||||
or receipt["grant_sha256"] != self.grant.digest or receipt["identity"] not in {self.grant.raw["status_identity"], self.grant.raw["merge_identity"]}
|
||||
or receipt["token_name"] not in {f"{expected_name_prefix}-status", f"{expected_name_prefix}-merge"}
|
||||
or receipt["credential_ref"] != expected_credential_ref
|
||||
or hashlib.sha256(canonical(unsigned_receipt)).hexdigest() != receipt["audit_sha256"]
|
||||
or not hmac.compare_digest(receipt["audit_hmac_signature"], hmac.new(self.audit_key, canonical(unsigned_receipt), hashlib.sha256).hexdigest())):
|
||||
raise ContractDenied("PROVISION_RECEIPT_INVALID")
|
||||
|
||||
def _audit(self, value: dict) -> dict:
|
||||
raw = canonical(value)
|
||||
result = dict(value)
|
||||
result["audit_sha256"] = hashlib.sha256(raw).hexdigest()
|
||||
result["audit_hmac_signature"] = hmac.new(self.audit_key, raw, hashlib.sha256).hexdigest()
|
||||
self.audit_dir.mkdir(mode=0o750, parents=True, exist_ok=True)
|
||||
destination = self.audit_dir / f"{value['operation']}-{value['token_name']}.json"
|
||||
descriptor = os.open(destination, os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0), 0o640)
|
||||
try:
|
||||
os.write(descriptor, canonical(result) + b"\n")
|
||||
os.fsync(descriptor)
|
||||
finally:
|
||||
os.close(descriptor)
|
||||
return result
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(prog="aios-task-credential-broker")
|
||||
parser.add_argument("--grant", required=True, type=Path)
|
||||
parser.add_argument("--audit-dir", required=True, type=Path)
|
||||
parser.add_argument("--operation", choices=("provision", "revoke"))
|
||||
parser.add_argument("--identity")
|
||||
parser.add_argument("--token-name")
|
||||
parser.add_argument("--encrypted-path", type=Path)
|
||||
parser.add_argument("--receipt", type=Path)
|
||||
parser.add_argument("--request", type=Path)
|
||||
parser.add_argument("--grant-signature", required=True, type=Path)
|
||||
parser.add_argument("--allowed-signers", required=True, type=Path)
|
||||
parser.add_argument("--audit-key", required=True, type=Path)
|
||||
args = parser.parse_args(argv)
|
||||
grant = load_signed_task_grant(args.grant, args.grant_signature, args.allowed_signers)
|
||||
broker = TaskCredentialBroker(grant, args.audit_dir, read_runtime_secret(args.audit_key))
|
||||
if args.request is not None:
|
||||
request = json.loads(args.request.read_text(encoding="utf-8"))
|
||||
if not isinstance(request, dict) or set(request) not in ({"operation", "identity", "token_name", "encrypted_path"}, {"operation", "identity", "token_name", "encrypted_path", "receipt"}):
|
||||
raise ContractDenied("CREDENTIAL_REQUEST_INVALID")
|
||||
args.operation = request["operation"]
|
||||
args.identity = request["identity"]
|
||||
args.token_name = request["token_name"]
|
||||
args.encrypted_path = Path(request["encrypted_path"])
|
||||
args.receipt = Path(request["receipt"]) if "receipt" in request else None
|
||||
if args.operation is None or args.identity is None or args.token_name is None or args.encrypted_path is None:
|
||||
raise ContractDenied("CREDENTIAL_REQUEST_INCOMPLETE")
|
||||
if args.operation == "provision":
|
||||
result = broker.provision(identity=args.identity, token_name=args.token_name, encrypted_path=args.encrypted_path)
|
||||
else:
|
||||
if args.receipt is None:
|
||||
raise ContractDenied("RECEIPT_REQUIRED")
|
||||
result = broker.revoke(receipt=json.loads(args.receipt.read_text(encoding="utf-8")), encrypted_path=args.encrypted_path)
|
||||
print(json.dumps(result, sort_keys=True, separators=(",", ":")))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from datetime import datetime, timezone
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sqlite3
|
||||
import subprocess
|
||||
|
||||
from .task_credential_broker import SYSTEMD_CREDS, TaskCredentialBroker
|
||||
from .task_status_merge_broker import ContractDenied, load_signed_task_grant
|
||||
|
||||
|
||||
RUNTIME_ROOT = Path("/var/lib/aios-gitops/runtime/task-status-merge")
|
||||
CREDENTIAL_ROOT = Path("/run/aios-task-credential-broker")
|
||||
|
||||
|
||||
def _operation_complete(task_dir: Path, token_name: str) -> bool:
|
||||
if token_name.endswith("-status"):
|
||||
journal, operation = task_dir / "status-journal" / "journal.sqlite3", "statuses"
|
||||
elif token_name.endswith("-merge"):
|
||||
journal, operation = task_dir / "merge-journal" / "journal.sqlite3", "merge"
|
||||
else:
|
||||
raise ContractDenied("SWEEPER_TOKEN_NAME_INVALID")
|
||||
if not journal.is_file() or journal.is_symlink():
|
||||
return False
|
||||
connection = sqlite3.connect(f"file:{journal}?mode=ro", uri=True)
|
||||
try:
|
||||
row = connection.execute("SELECT state FROM quota WHERE operation=?", (operation,)).fetchone()
|
||||
finally:
|
||||
connection.close()
|
||||
return row == ("COMMITTED",)
|
||||
|
||||
|
||||
def sweep(runtime_root: Path, allowed_signers: Path, *, now: datetime | None = None) -> list[dict]:
|
||||
if os.geteuid() != 0:
|
||||
raise ContractDenied("ROOT_SWEEPER_REQUIRED")
|
||||
if runtime_root != RUNTIME_ROOT or runtime_root.is_symlink():
|
||||
raise ContractDenied("SWEEPER_ROOT_INVALID")
|
||||
current = now or datetime.now(timezone.utc)
|
||||
results: list[dict] = []
|
||||
for task_dir in sorted(runtime_root.iterdir() if runtime_root.exists() else []):
|
||||
if not task_dir.is_dir() or task_dir.is_symlink():
|
||||
raise ContractDenied("SWEEPER_TASK_DIR_INVALID")
|
||||
grant_path, signature_path = task_dir / "grant.json", task_dir / "grant.sig"
|
||||
if not grant_path.is_file() or not signature_path.is_file():
|
||||
raise ContractDenied("SWEEPER_GRANT_INCOMPLETE")
|
||||
grant = load_signed_task_grant(grant_path, signature_path, allowed_signers, now=current, allow_expired=True)
|
||||
audit_credential = CREDENTIAL_ROOT / f"{task_dir.name}-audit.cred"
|
||||
if not audit_credential.is_file() or audit_credential.is_symlink():
|
||||
raise ContractDenied("SWEEPER_TASK_AUDIT_CREDENTIAL_MISSING")
|
||||
audit_key = subprocess.run(
|
||||
[str(SYSTEMD_CREDS), "decrypt", str(audit_credential), "-"], check=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
).stdout.strip()
|
||||
if not audit_key or b"\n" in audit_key or b"\x00" in audit_key:
|
||||
raise ContractDenied("SWEEPER_TASK_AUDIT_KEY_INVALID")
|
||||
broker = TaskCredentialBroker(grant, task_dir / "audit", audit_key)
|
||||
for receipt_path in sorted((task_dir / "audit").glob("credential_provision-*.json")):
|
||||
if receipt_path.is_symlink():
|
||||
raise ContractDenied("SWEEPER_RECEIPT_UNSAFE")
|
||||
receipt = json.loads(receipt_path.read_text(encoding="utf-8"))
|
||||
token_name = str(receipt.get("token_name") or "")
|
||||
credential_ref = str(receipt.get("credential_ref") or "")
|
||||
prefix = "systemd-credential-encrypted://"
|
||||
if not credential_ref.startswith(prefix):
|
||||
raise ContractDenied("SWEEPER_CREDENTIAL_REF_INVALID")
|
||||
encrypted_path = CREDENTIAL_ROOT / credential_ref.removeprefix(prefix)
|
||||
revoked_receipt = task_dir / "audit" / f"credential_revoke-{token_name}.json"
|
||||
if revoked_receipt.exists():
|
||||
if encrypted_path.exists():
|
||||
raise ContractDenied("SWEEPER_REVOKED_PROJECTION_REAPPEARED")
|
||||
continue
|
||||
if current < grant.expires_at and not _operation_complete(task_dir, token_name):
|
||||
continue
|
||||
if not encrypted_path.is_file() or encrypted_path.is_symlink():
|
||||
raise ContractDenied("SWEEPER_CREDENTIAL_PROJECTION_MISSING")
|
||||
results.append(broker.revoke(receipt=receipt, encrypted_path=encrypted_path))
|
||||
return results
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(prog="aios-task-credential-expiry-sweeper")
|
||||
parser.add_argument("--runtime-root", required=True, type=Path)
|
||||
parser.add_argument("--allowed-signers", required=True, type=Path)
|
||||
args = parser.parse_args(argv)
|
||||
result = sweep(args.runtime_root, args.allowed_signers)
|
||||
print(json.dumps({"status": "PASS", "revoked": result}, sort_keys=True, separators=(",", ":")))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+1090
File diff suppressed because it is too large
Load Diff
+472
@@ -0,0 +1,472 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sqlite3
|
||||
import stat
|
||||
import subprocess
|
||||
from typing import Any, Protocol
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
|
||||
ALLOWED_CONTEXTS = frozenset({
|
||||
"aios/branch-protection",
|
||||
"aios/evidence-closure",
|
||||
"aios/secret-scan",
|
||||
"aios/state-machine",
|
||||
"aios/validator",
|
||||
})
|
||||
ALLOWED_IDENTITIES = frozenset({"codex", "gpt"})
|
||||
OID = re.compile(r"^[0-9a-f]{40}$")
|
||||
SHA256 = re.compile(r"^[0-9a-f]{64}$")
|
||||
SECRET_REF = re.compile(r"^systemd-credential://[A-Za-z0-9_.-]+$")
|
||||
SUPERSEDED_PR_NUMBER = 36
|
||||
SUPERSEDED_HEAD_OID = "a8644fc172bd8ebbba17a16e6bf586c93fd1e793"
|
||||
FIELDS = frozenset({
|
||||
"schema_id", "schema_version", "repository", "pr_number", "base_oid",
|
||||
"target_commit", "approved_tree_oid", "manifest_path", "manifest_blob_oid", "manifest_sha256",
|
||||
"payload_sha256", "status_identity", "merge_identity", "status_contexts",
|
||||
"status_write_quota", "merge_quota", "merge_method", "branch_push_allowed",
|
||||
"tag_write_allowed", "main_write_allowed", "registry_write_allowed",
|
||||
"baseline_write_allowed", "production_write_allowed", "issued_at", "expires_at",
|
||||
"audit_key_ref", "grant_signer_identity", "grant_signature_namespace",
|
||||
"status_evidence_sha256", "supersedes_pr_number", "supersedes_head_oid",
|
||||
"renewal_required", "timestamp_contract", "canonical_timezone",
|
||||
"raw_timestamp_preserved", "naive_datetime_allowed",
|
||||
})
|
||||
RFC3339 = re.compile(
|
||||
r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$"
|
||||
)
|
||||
|
||||
|
||||
class ContractDenied(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def canonical(value: Any) -> bytes:
|
||||
return json.dumps(value, sort_keys=True, separators=(",", ":")).encode()
|
||||
|
||||
|
||||
def _time(value: str) -> datetime:
|
||||
if not isinstance(value, str) or value != value.strip() or not RFC3339.fullmatch(value):
|
||||
raise ContractDenied("TIMESTAMP_INVALID")
|
||||
try:
|
||||
normalized = value[:-1] + "+00:00" if value.endswith("Z") else value
|
||||
parsed = datetime.fromisoformat(normalized)
|
||||
except ValueError as exc:
|
||||
raise ContractDenied("TIMESTAMP_INVALID") from exc
|
||||
if parsed.tzinfo is None or parsed.utcoffset() is None:
|
||||
raise ContractDenied("TIMESTAMP_TIMEZONE_REQUIRED")
|
||||
return parsed.astimezone(timezone.utc)
|
||||
|
||||
|
||||
def _utc_timestamp(value: datetime) -> str:
|
||||
normalized = value.astimezone(timezone.utc).isoformat()
|
||||
return normalized[:-6] + "Z" if normalized.endswith("+00:00") else normalized
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TaskGrant:
|
||||
raw: dict[str, Any]
|
||||
issued_at: datetime
|
||||
expires_at: datetime
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, value: dict[str, Any], *, now: datetime | None = None,
|
||||
allow_expired: bool = False) -> "TaskGrant":
|
||||
if not isinstance(value, dict) or set(value) != FIELDS:
|
||||
raise ContractDenied("GRANT_FIELDS_INVALID")
|
||||
if value["schema_id"] != "AIOS_TASK_STATUS_MERGE_GRANT" or value["schema_version"] != 1:
|
||||
raise ContractDenied("GRANT_SCHEMA_INVALID")
|
||||
if value["repository"] != "aios/aios" or type(value["pr_number"]) is not int or value["pr_number"] <= 0:
|
||||
raise ContractDenied("REPOSITORY_OR_PR_MISMATCH")
|
||||
for field in ("base_oid", "target_commit", "approved_tree_oid", "manifest_blob_oid"):
|
||||
if not isinstance(value[field], str) or not OID.fullmatch(value[field]):
|
||||
raise ContractDenied("OID_INVALID")
|
||||
if not isinstance(value["manifest_path"], str) or not re.fullmatch(r"approvals/[A-Za-z0-9._/-]+\.json", value["manifest_path"]) or ".." in value["manifest_path"].split("/"):
|
||||
raise ContractDenied("MANIFEST_PATH_INVALID")
|
||||
for field in ("manifest_sha256", "payload_sha256"):
|
||||
if not isinstance(value[field], str) or not SHA256.fullmatch(value[field]):
|
||||
raise ContractDenied("SHA256_INVALID")
|
||||
evidence = value["status_evidence_sha256"]
|
||||
if not isinstance(evidence, dict) or set(evidence) != ALLOWED_CONTEXTS or any(not isinstance(item, str) or not SHA256.fullmatch(item) for item in evidence.values()):
|
||||
raise ContractDenied("STATUS_EVIDENCE_BINDING_INVALID")
|
||||
if value["supersedes_pr_number"] != SUPERSEDED_PR_NUMBER or value["pr_number"] == SUPERSEDED_PR_NUMBER:
|
||||
raise ContractDenied("RENEWAL_PR_INVALID")
|
||||
if value["supersedes_head_oid"] != SUPERSEDED_HEAD_OID or value["supersedes_head_oid"] == value["target_commit"] or value["renewal_required"] is not True:
|
||||
raise ContractDenied("RENEWAL_BINDING_INVALID")
|
||||
if value["status_identity"] not in ALLOWED_IDENTITIES or value["merge_identity"] not in ALLOWED_IDENTITIES:
|
||||
raise ContractDenied("IDENTITY_NOT_ALLOWED")
|
||||
if set(value["status_contexts"]) != ALLOWED_CONTEXTS or len(value["status_contexts"]) != 5:
|
||||
raise ContractDenied("STATUS_CONTEXTS_INVALID")
|
||||
if value["status_write_quota"] != 5 or value["merge_quota"] != 1:
|
||||
raise ContractDenied("QUOTA_INVALID")
|
||||
if value["merge_method"] != "fast-forward-only":
|
||||
raise ContractDenied("MERGE_METHOD_INVALID")
|
||||
denied = (
|
||||
"branch_push_allowed", "tag_write_allowed", "main_write_allowed",
|
||||
"registry_write_allowed", "baseline_write_allowed", "production_write_allowed",
|
||||
)
|
||||
if any(value[field] is not False for field in denied):
|
||||
raise ContractDenied("WRITE_SCOPE_EXPANDED")
|
||||
if not isinstance(value["audit_key_ref"], str) or not SECRET_REF.fullmatch(value["audit_key_ref"]):
|
||||
raise ContractDenied("AUDIT_KEY_REFERENCE_INVALID")
|
||||
if value["grant_signer_identity"] != "aios-task-grant-signer" or value["grant_signature_namespace"] != "aios-task-status-merge-grant-v1":
|
||||
raise ContractDenied("GRANT_SIGNATURE_POLICY_INVALID")
|
||||
if (value["timestamp_contract"] != "RFC3339_OFFSET_AWARE_V2"
|
||||
or value["canonical_timezone"] != "UTC"
|
||||
or value["raw_timestamp_preserved"] is not True
|
||||
or value["naive_datetime_allowed"] is not False):
|
||||
raise ContractDenied("TIMESTAMP_CONTRACT_INVALID")
|
||||
issued, expires = _time(value["issued_at"]), _time(value["expires_at"])
|
||||
current = now or datetime.now(timezone.utc)
|
||||
lifetime = (expires - issued).total_seconds()
|
||||
if lifetime <= 0 or lifetime > 12 * 3600 or (not allow_expired and current >= expires):
|
||||
raise ContractDenied("GRANT_EXPIRED_OR_LIFETIME_INVALID")
|
||||
return cls(dict(value), issued, expires)
|
||||
|
||||
@property
|
||||
def digest(self) -> str:
|
||||
return hashlib.sha256(canonical(self.raw)).hexdigest()
|
||||
|
||||
|
||||
def load_signed_task_grant(grant_path: Path, signature_path: Path, allowed_signers: Path,
|
||||
*, now: datetime | None = None, allow_expired: bool = False) -> TaskGrant:
|
||||
for path in (grant_path, signature_path, allowed_signers):
|
||||
if not path.is_absolute() or path.is_symlink():
|
||||
raise ContractDenied("GRANT_SIGNATURE_PATH_UNSAFE")
|
||||
raw = grant_path.read_bytes()
|
||||
if len(raw) > 1024 * 1024:
|
||||
raise ContractDenied("GRANT_TOO_LARGE")
|
||||
try:
|
||||
value = json.loads(raw)
|
||||
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
|
||||
raise ContractDenied("GRANT_JSON_INVALID") from exc
|
||||
if raw != canonical(value) + b"\n":
|
||||
raise ContractDenied("GRANT_NOT_CANONICAL")
|
||||
grant = TaskGrant.from_dict(value, now=now, allow_expired=allow_expired)
|
||||
command = [
|
||||
"/usr/bin/ssh-keygen", "-Y", "verify", "-f", str(allowed_signers),
|
||||
"-I", "aios-task-grant-signer", "-n", "aios-task-status-merge-grant-v1",
|
||||
"-s", str(signature_path),
|
||||
]
|
||||
try:
|
||||
subprocess.run(command, input=raw, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
||||
except (OSError, subprocess.CalledProcessError) as exc:
|
||||
raise ContractDenied("GRANT_SIGNATURE_INVALID") from exc
|
||||
return grant
|
||||
|
||||
|
||||
class API(Protocol):
|
||||
def get(self, path: str) -> Any: ...
|
||||
def post(self, path: str, payload: dict[str, Any]) -> Any: ...
|
||||
|
||||
|
||||
def read_runtime_secret(path: Path) -> bytes:
|
||||
flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0)
|
||||
descriptor = os.open(path, flags)
|
||||
try:
|
||||
metadata = os.fstat(descriptor)
|
||||
mode = stat.S_IMODE(metadata.st_mode)
|
||||
credential_directory = os.environ.get("CREDENTIALS_DIRECTORY")
|
||||
systemd_projection = (
|
||||
credential_directory is not None
|
||||
and path.parent == Path(credential_directory)
|
||||
and mode == 0o440
|
||||
and metadata.st_uid == 0
|
||||
and metadata.st_gid == 0
|
||||
)
|
||||
if not stat.S_ISREG(metadata.st_mode) or (mode != 0o400 and not systemd_projection):
|
||||
raise ContractDenied("CREDENTIAL_FILE_UNSAFE")
|
||||
value = os.read(descriptor, 16 * 1024 + 1)
|
||||
finally:
|
||||
os.close(descriptor)
|
||||
if not value or len(value) > 16 * 1024 or b"\x00" in value or b"\n" in value.strip():
|
||||
raise ContractDenied("CREDENTIAL_CONTENT_INVALID")
|
||||
return value.strip()
|
||||
|
||||
|
||||
class _NoRedirect(urllib.request.HTTPRedirectHandler):
|
||||
def redirect_request(self, req, fp, code, msg, headers, newurl):
|
||||
return None
|
||||
|
||||
|
||||
class GiteaClient:
|
||||
def __init__(self, token_path: Path, api_base: str = "http://127.0.0.1:3000/api/v1"):
|
||||
parsed = urllib.parse.urlparse(api_base)
|
||||
if parsed.scheme != "http" or parsed.hostname != "127.0.0.1" or parsed.path.rstrip("/") != "/api/v1" or parsed.username or parsed.password or parsed.query or parsed.fragment:
|
||||
raise ContractDenied("GITEA_API_ORIGIN_INVALID")
|
||||
self.token_path = Path(token_path)
|
||||
self.api_base = api_base.rstrip("/")
|
||||
self.opener = urllib.request.build_opener(_NoRedirect)
|
||||
|
||||
def _request(self, method: str, path: str, payload: dict[str, Any] | None = None) -> Any:
|
||||
if not path.startswith("/") or ".." in path.split("/"):
|
||||
raise ContractDenied("GITEA_API_PATH_INVALID")
|
||||
token = read_runtime_secret(self.token_path).decode("utf-8")
|
||||
body = canonical(payload) if payload is not None else None
|
||||
request = urllib.request.Request(self.api_base + path, data=body, method=method, headers={"Authorization": f"token {token}", "Content-Type": "application/json", "Accept": "application/json"})
|
||||
try:
|
||||
with self.opener.open(request, timeout=20) as response:
|
||||
raw = response.read(2 * 1024 * 1024 + 1)
|
||||
except (urllib.error.URLError, OSError) as exc:
|
||||
raise ContractDenied(f"GITEA_API_FAILED:{type(exc).__name__}") from exc
|
||||
if len(raw) > 2 * 1024 * 1024:
|
||||
raise ContractDenied("GITEA_API_RESPONSE_TOO_LARGE")
|
||||
return json.loads(raw.decode("utf-8")) if raw else None
|
||||
|
||||
def get(self, path: str) -> Any:
|
||||
return self._request("GET", path)
|
||||
|
||||
def post(self, path: str, payload: dict[str, Any]) -> Any:
|
||||
return self._request("POST", path, payload)
|
||||
|
||||
|
||||
class QuotaJournal:
|
||||
def __init__(self, path: Path, grant: TaskGrant):
|
||||
self.path = Path(path)
|
||||
self.grant = grant
|
||||
self.connection = sqlite3.connect(self.path, isolation_level=None)
|
||||
journal_mode = self.connection.execute("PRAGMA journal_mode=WAL").fetchone()
|
||||
if journal_mode is None or str(journal_mode[0]).lower() != "wal":
|
||||
raise ContractDenied("JOURNAL_MODE_INVALID")
|
||||
self.connection.execute("PRAGMA synchronous=FULL")
|
||||
synchronous = self.connection.execute("PRAGMA synchronous").fetchone()
|
||||
if synchronous is None or int(synchronous[0]) != 2:
|
||||
raise ContractDenied("JOURNAL_SYNCHRONOUS_INVALID")
|
||||
self.connection.execute("CREATE TABLE IF NOT EXISTS quota(operation TEXT PRIMARY KEY, state TEXT NOT NULL, effect_sha256 TEXT)")
|
||||
self.connection.execute("CREATE TABLE IF NOT EXISTS audit(seq INTEGER PRIMARY KEY AUTOINCREMENT, event BLOB NOT NULL, previous_hash TEXT NOT NULL, event_hash TEXT NOT NULL UNIQUE)")
|
||||
|
||||
def close(self) -> None:
|
||||
self.connection.close()
|
||||
|
||||
def reserve(self, operation: str) -> str:
|
||||
limit = {"statuses": 1, "merge": 1}.get(operation)
|
||||
if limit is None:
|
||||
raise ContractDenied("OPERATION_INVALID")
|
||||
self.connection.execute("BEGIN IMMEDIATE")
|
||||
try:
|
||||
row = self.connection.execute("SELECT state FROM quota WHERE operation=?", (operation,)).fetchone()
|
||||
if row and row[0] == "COMMITTED":
|
||||
raise ContractDenied("STATUS_QUOTA_EXHAUSTED" if operation == "statuses" else "MERGE_QUOTA_EXHAUSTED")
|
||||
if row and row[0] != "RESERVED":
|
||||
raise ContractDenied("JOURNAL_STATE_INVALID")
|
||||
if not row:
|
||||
self.connection.execute("INSERT INTO quota(operation,state,effect_sha256) VALUES(?,'RESERVED',NULL)", (operation,))
|
||||
self.connection.execute("COMMIT")
|
||||
return "RESERVED"
|
||||
except Exception:
|
||||
self.connection.execute("ROLLBACK")
|
||||
raise
|
||||
|
||||
def state(self, operation: str) -> str | None:
|
||||
row = self.connection.execute("SELECT state FROM quota WHERE operation=?", (operation,)).fetchone()
|
||||
return str(row[0]) if row else None
|
||||
|
||||
def commit(self, operation: str, effect: dict[str, Any]) -> None:
|
||||
digest = hashlib.sha256(canonical(effect)).hexdigest()
|
||||
self.connection.execute("BEGIN IMMEDIATE")
|
||||
try:
|
||||
row = self.connection.execute("SELECT state,effect_sha256 FROM quota WHERE operation=?", (operation,)).fetchone()
|
||||
if row is None or row[0] not in {"RESERVED", "COMMITTED"}:
|
||||
raise ContractDenied("RESERVATION_MISSING")
|
||||
if row[0] == "COMMITTED" and row[1] != digest:
|
||||
raise ContractDenied("COMMITTED_EFFECT_MISMATCH")
|
||||
self.connection.execute("UPDATE quota SET state='COMMITTED',effect_sha256=? WHERE operation=?", (digest, operation))
|
||||
self.connection.execute("COMMIT")
|
||||
except Exception:
|
||||
self.connection.execute("ROLLBACK")
|
||||
raise
|
||||
|
||||
def append(self, event: dict[str, Any]) -> str:
|
||||
row = self.connection.execute("SELECT event_hash FROM audit ORDER BY seq DESC LIMIT 1").fetchone()
|
||||
previous = row[0] if row else "0" * 64
|
||||
raw = canonical(event)
|
||||
digest = hashlib.sha256(previous.encode() + raw).hexdigest()
|
||||
self.connection.execute("INSERT INTO audit(event,previous_hash,event_hash) VALUES(?,?,?)", (raw, previous, digest))
|
||||
return digest
|
||||
|
||||
def verify_chain(self) -> bool:
|
||||
previous = "0" * 64
|
||||
for raw, observed_previous, observed_hash in self.connection.execute("SELECT event,previous_hash,event_hash FROM audit ORDER BY seq"):
|
||||
if observed_previous != previous or hashlib.sha256(previous.encode() + raw).hexdigest() != observed_hash:
|
||||
return False
|
||||
previous = observed_hash
|
||||
return True
|
||||
|
||||
|
||||
def _latest_statuses(values: Any) -> dict[str, dict[str, Any]]:
|
||||
if not isinstance(values, list):
|
||||
raise ContractDenied("STATUS_RESPONSE_INVALID")
|
||||
latest: dict[str, tuple[tuple[str, int], dict[str, Any]]] = {}
|
||||
for item in values:
|
||||
if not isinstance(item, dict) or item.get("context") not in ALLOWED_CONTEXTS:
|
||||
continue
|
||||
key = (str(item.get("created_at") or ""), int(item.get("id") or 0))
|
||||
context = item["context"]
|
||||
if context not in latest or key > latest[context][0]:
|
||||
latest[context] = (key, item)
|
||||
return {context: item for context, (_, item) in latest.items()}
|
||||
|
||||
|
||||
def _verify_owner_binding(api: API, grant: TaskGrant) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
pr_number = grant.raw["pr_number"]
|
||||
head, base = grant.raw["target_commit"], grant.raw["base_oid"]
|
||||
pr = api.get(f"/repos/aios/aios/pulls/{pr_number}")
|
||||
reviews = api.get(f"/repos/aios/aios/pulls/{pr_number}/reviews")
|
||||
if not isinstance(pr, dict) or pr.get("state") != "open" or pr.get("mergeable") is not True or (pr.get("head") or {}).get("sha") != head or (pr.get("base") or {}).get("sha") != base or (pr.get("base") or {}).get("ref") != "governance/approvals":
|
||||
raise ContractDenied("PR_BINDING_INVALID")
|
||||
if not isinstance(reviews, list):
|
||||
raise ContractDenied("REVIEW_RESPONSE_INVALID")
|
||||
owners = [item for item in reviews if isinstance(item, dict) and (item.get("user") or {}).get("login") == "owner" and (item.get("user") or {}).get("id") == 2]
|
||||
if not owners:
|
||||
raise ContractDenied("OWNER_EXACT_HEAD_APPROVAL_MISSING")
|
||||
latest = max(owners, key=lambda item: int(item.get("id") or -1))
|
||||
if latest.get("state") != "APPROVED" or latest.get("commit_id") != head or latest.get("official") is not True or latest.get("stale") is not False or latest.get("dismissed") is not False:
|
||||
raise ContractDenied("OWNER_EXACT_HEAD_APPROVAL_MISSING")
|
||||
raw_timestamp = str(latest.get("submitted_at") or "")
|
||||
submitted = _time(raw_timestamp)
|
||||
if submitted < grant.issued_at or submitted >= grant.expires_at:
|
||||
raise ContractDenied("OWNER_APPROVAL_EXPIRED")
|
||||
normalized_review = dict(latest)
|
||||
normalized_review["review_timestamp_raw"] = raw_timestamp
|
||||
normalized_review["review_timestamp_utc"] = _utc_timestamp(submitted)
|
||||
return pr, normalized_review
|
||||
|
||||
|
||||
def _signed_receipt(value: dict[str, Any], key: bytes) -> dict[str, Any]:
|
||||
if not key:
|
||||
raise ContractDenied("AUDIT_KEY_EMPTY")
|
||||
result = dict(value)
|
||||
result["audit_signature"] = hmac.new(key, canonical(value), hashlib.sha256).hexdigest()
|
||||
return result
|
||||
|
||||
|
||||
class StatusBroker:
|
||||
def __init__(self, grant: TaskGrant, journal: QuotaJournal, api: API, audit_key: bytes):
|
||||
self.grant, self.journal, self.api, self.audit_key = grant, journal, api, audit_key
|
||||
|
||||
def publish(self, evidence: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
if not isinstance(evidence, list) or len(evidence) != 5:
|
||||
raise ContractDenied("EVIDENCE_SET_INVALID")
|
||||
mapped = {item.get("context"): item for item in evidence if isinstance(item, dict)}
|
||||
if set(mapped) != ALLOWED_CONTEXTS or any(set(item) != {"context", "evidence_sha256"} or not SHA256.fullmatch(str(item["evidence_sha256"])) or item["evidence_sha256"] != self.grant.raw["status_evidence_sha256"].get(context) for context, item in mapped.items()):
|
||||
raise ContractDenied("EVIDENCE_SET_INVALID")
|
||||
_, review = _verify_owner_binding(self.api, self.grant)
|
||||
self.journal.reserve("statuses")
|
||||
published = []
|
||||
head = self.grant.raw["target_commit"]
|
||||
pr_number = self.grant.raw["pr_number"]
|
||||
observed_before = self.api.get(f"/repos/aios/aios/commits/{head}/statuses")
|
||||
already = _latest_statuses(observed_before)
|
||||
already = {context: item for context, item in already.items()
|
||||
if item.get("status", item.get("state")) == "success"
|
||||
and item.get("sha", head) == head
|
||||
and (item.get("creator") or {}).get("login") == self.grant.raw["status_identity"]
|
||||
and str(item.get("description") or "").endswith(mapped[context]["evidence_sha256"])}
|
||||
for context in sorted(ALLOWED_CONTEXTS):
|
||||
digest = mapped[context]["evidence_sha256"]
|
||||
if context in already:
|
||||
published.append({"context": context, "evidence_sha256": digest, "status_id": already[context].get("id"), "reconciled": True})
|
||||
continue
|
||||
response = self.api.post(f"/repos/aios/aios/statuses/{head}", {
|
||||
"state": "success", "context": context,
|
||||
"description": f"task-scoped PASS evidence_sha256={digest}",
|
||||
"target_url": f"https://g.cnnetmkt.top/git/aios/aios/pulls/{pr_number}",
|
||||
})
|
||||
if not isinstance(response, dict) or response.get("context") != context or response.get("status", response.get("state")) != "success":
|
||||
raise ContractDenied("STATUS_PUBLICATION_FAILED")
|
||||
published.append({"context": context, "evidence_sha256": digest, "status_id": response.get("id")})
|
||||
observed = self.api.get(f"/repos/aios/aios/commits/{head}/statuses")
|
||||
latest = _latest_statuses(observed)
|
||||
if set(latest) != ALLOWED_CONTEXTS or any(
|
||||
item.get("status", item.get("state")) != "success"
|
||||
or item.get("sha", head) != head
|
||||
or (item.get("creator") or {}).get("login") != self.grant.raw["status_identity"]
|
||||
or not str(item.get("description") or "").endswith(mapped[context]["evidence_sha256"])
|
||||
for context, item in latest.items()
|
||||
):
|
||||
raise ContractDenied("STATUS_READBACK_INVALID")
|
||||
receipt = {"status": "PASS", "operation": "publish_statuses", "grant_sha256": self.grant.digest, "target_commit": head, "published_count": 5, "published": published, "review_timestamp_raw": review["review_timestamp_raw"], "review_timestamp_utc": review["review_timestamp_utc"]}
|
||||
receipt["audit_signature"] = hmac.new(self.audit_key, canonical(receipt), hashlib.sha256).hexdigest()
|
||||
receipt["audit_chain_hash"] = self.journal.append(receipt)
|
||||
self.journal.commit("statuses", receipt)
|
||||
return receipt
|
||||
|
||||
|
||||
class MergeBroker:
|
||||
def __init__(self, grant: TaskGrant, journal: QuotaJournal, api: API, audit_key: bytes):
|
||||
self.grant, self.journal, self.api, self.audit_key = grant, journal, api, audit_key
|
||||
|
||||
def merge(self) -> dict[str, Any]:
|
||||
head, base = self.grant.raw["target_commit"], self.grant.raw["base_oid"]
|
||||
pr_number = self.grant.raw["pr_number"]
|
||||
pr = self.api.get(f"/repos/aios/aios/pulls/{pr_number}")
|
||||
if isinstance(pr, dict) and pr.get("merged") is True:
|
||||
observed_branch = self.api.get("/repos/aios/aios/branches/governance%2Fapprovals")
|
||||
if pr.get("merge_commit_sha") != head or (observed_branch.get("commit") or {}).get("id") != head or self.journal.state("merge") != "RESERVED":
|
||||
raise ContractDenied("FAST_FORWARD_RESULT_MISMATCH")
|
||||
receipt = {"status": "PASS", "operation": "fast_forward_merge", "grant_sha256": self.grant.digest, "pr_number": pr_number, "governance_oid": head, "merge_method": "fast-forward-only", "reconciled": True}
|
||||
receipt["audit_signature"] = hmac.new(self.audit_key, canonical(receipt), hashlib.sha256).hexdigest()
|
||||
receipt["audit_chain_hash"] = self.journal.append(receipt)
|
||||
self.journal.commit("merge", receipt)
|
||||
return receipt
|
||||
pr, review = _verify_owner_binding(self.api, self.grant)
|
||||
statuses = self.api.get(f"/repos/aios/aios/commits/{head}/statuses")
|
||||
latest = _latest_statuses(statuses)
|
||||
if set(latest) != ALLOWED_CONTEXTS or any(item.get("status", item.get("state")) != "success" or item.get("sha", head) != head or (item.get("creator") or {}).get("login") != self.grant.raw["status_identity"] or not str(item.get("description") or "").endswith(self.grant.raw["status_evidence_sha256"][context]) for context, item in latest.items()):
|
||||
raise ContractDenied("MACHINE_STATUS_INVALID")
|
||||
self.journal.reserve("merge")
|
||||
self.api.post(f"/repos/aios/aios/pulls/{pr_number}/merge", {"Do": "fast-forward-only", "head_commit_id": head, "force_merge": False, "delete_branch_after_merge": False})
|
||||
observed_pr = self.api.get(f"/repos/aios/aios/pulls/{pr_number}")
|
||||
observed_branch = self.api.get("/repos/aios/aios/branches/governance%2Fapprovals")
|
||||
if observed_pr.get("merged") is not True or observed_pr.get("merge_commit_sha") != head or (observed_branch.get("commit") or {}).get("id") != head:
|
||||
raise ContractDenied("FAST_FORWARD_RESULT_MISMATCH")
|
||||
receipt = {"status": "PASS", "operation": "fast_forward_merge", "grant_sha256": self.grant.digest, "pr_number": pr_number, "governance_oid": head, "merge_method": "fast-forward-only", "review_timestamp_raw": review["review_timestamp_raw"], "review_timestamp_utc": review["review_timestamp_utc"]}
|
||||
receipt["audit_signature"] = hmac.new(self.audit_key, canonical(receipt), hashlib.sha256).hexdigest()
|
||||
receipt["audit_chain_hash"] = self.journal.append(receipt)
|
||||
self.journal.commit("merge", receipt)
|
||||
return receipt
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(prog="aios-task-status-merge-broker")
|
||||
parser.add_argument("--mode", required=True, choices=("status", "merge"))
|
||||
parser.add_argument("--grant", required=True, type=Path)
|
||||
parser.add_argument("--token", required=True, type=Path)
|
||||
parser.add_argument("--audit-key", required=True, type=Path)
|
||||
parser.add_argument("--grant-signature", required=True, type=Path)
|
||||
parser.add_argument("--allowed-signers", required=True, type=Path)
|
||||
parser.add_argument("--journal", required=True, type=Path)
|
||||
parser.add_argument("--evidence", type=Path)
|
||||
args = parser.parse_args(argv)
|
||||
grant = load_signed_task_grant(args.grant, args.grant_signature, args.allowed_signers)
|
||||
journal = QuotaJournal(args.journal, grant)
|
||||
try:
|
||||
client = GiteaClient(args.token)
|
||||
key = read_runtime_secret(args.audit_key)
|
||||
if args.mode == "status":
|
||||
if args.evidence is None:
|
||||
raise ContractDenied("EVIDENCE_REQUIRED")
|
||||
result = StatusBroker(grant, journal, client, key).publish(json.loads(args.evidence.read_text(encoding="utf-8")))
|
||||
else:
|
||||
if args.evidence is not None:
|
||||
raise ContractDenied("EVIDENCE_FORBIDDEN")
|
||||
result = MergeBroker(grant, journal, client, key).merge()
|
||||
print(json.dumps(result, sort_keys=True, separators=(",", ":")))
|
||||
return 0
|
||||
finally:
|
||||
journal.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "AIOS_EXACT_TWO_PARENT_MERGE_CLOSURE_V1",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"schema_id",
|
||||
"schema_version",
|
||||
"status",
|
||||
"repository",
|
||||
"target_ref",
|
||||
"merge_mode",
|
||||
"force",
|
||||
"request_sha256",
|
||||
"expected_old_ref_oid",
|
||||
"approved_pr_head_oid",
|
||||
"approved_pr_tree_oid",
|
||||
"merge_commit_oid",
|
||||
"parent_oids",
|
||||
"tree_oid",
|
||||
"commit_message_sha256",
|
||||
"owner_review_exact_head",
|
||||
"required_statuses_exact_head",
|
||||
"quarantine_reproducibility",
|
||||
"authority_fsck",
|
||||
"gitea_fsck",
|
||||
"authority_ref_oid",
|
||||
"gitea_ref_oid",
|
||||
"gitea_api_ref_oid",
|
||||
"audit_chain_hash",
|
||||
"audit_hmac_signature"
|
||||
],
|
||||
"properties": {
|
||||
"schema_id": {"const": "AIOS_EXACT_TWO_PARENT_MERGE_CLOSURE_V1"},
|
||||
"schema_version": {"const": 1},
|
||||
"status": {"const": "PASS"},
|
||||
"repository": {"const": "aios/aios"},
|
||||
"target_ref": {"const": "refs/heads/governance/approvals"},
|
||||
"merge_mode": {"const": "EXACT_TWO_PARENT_COMMIT"},
|
||||
"force": {"const": false},
|
||||
"request_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"expected_old_ref_oid": {"$ref": "#/$defs/oid"},
|
||||
"approved_pr_head_oid": {"$ref": "#/$defs/oid"},
|
||||
"approved_pr_tree_oid": {"$ref": "#/$defs/oid"},
|
||||
"merge_commit_oid": {"$ref": "#/$defs/oid"},
|
||||
"parent_oids": {
|
||||
"type": "array",
|
||||
"minItems": 2,
|
||||
"maxItems": 2,
|
||||
"items": {"$ref": "#/$defs/oid"}
|
||||
},
|
||||
"tree_oid": {"$ref": "#/$defs/oid"},
|
||||
"commit_message_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"owner_review_exact_head": {"const": true},
|
||||
"required_statuses_exact_head": {"const": true},
|
||||
"quarantine_reproducibility": {"const": "PASS"},
|
||||
"authority_fsck": {"const": "PASS"},
|
||||
"gitea_fsck": {"const": "PASS"},
|
||||
"authority_ref_oid": {"$ref": "#/$defs/oid"},
|
||||
"gitea_ref_oid": {"$ref": "#/$defs/oid"},
|
||||
"gitea_api_ref_oid": {"$ref": "#/$defs/oid"},
|
||||
"audit_chain_hash": {"$ref": "#/$defs/sha256"},
|
||||
"audit_hmac_signature": {"$ref": "#/$defs/sha256"}
|
||||
},
|
||||
"$defs": {
|
||||
"oid": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
|
||||
"sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"AIOS_EXACT_TWO_PARENT_MERGE_COMMIT_V1","type":"object","additionalProperties":false,"required":["schema_id","schema_version","repository","target_ref","merge_mode","operation_quota","force","pr_number","expected_old_ref_oid","approved_pr_head_oid","approved_pr_tree_oid","first_parent_oid","second_parent_oid","expected_merge_tree_oid","expected_merge_commit_oid","commit_message_utf8","commit_message_sha256","author_name","author_email","author_epoch","author_timezone","committer_name","committer_email","committer_epoch","committer_timezone","manifest_path","manifest_blob_oid","manifest_sha256","payload_sha256","status_identity","merge_identity","status_contexts","status_evidence_sha256","issued_at","expires_at","audit_key_ref","grant_signer_identity","grant_signature_namespace"],"properties":{"schema_id":{"const":"AIOS_EXACT_TWO_PARENT_MERGE_COMMIT_V1"},"schema_version":{"const":1},"repository":{"const":"aios/aios"},"target_ref":{"const":"refs/heads/governance/approvals"},"merge_mode":{"const":"EXACT_TWO_PARENT_COMMIT"},"operation_quota":{"const":1},"force":{"const":false},"pr_number":{"type":"integer","minimum":1},"expected_old_ref_oid":{"pattern":"^[0-9a-f]{40}$"},"approved_pr_head_oid":{"pattern":"^[0-9a-f]{40}$"},"approved_pr_tree_oid":{"pattern":"^[0-9a-f]{40}$"},"first_parent_oid":{"pattern":"^[0-9a-f]{40}$"},"second_parent_oid":{"pattern":"^[0-9a-f]{40}$"},"expected_merge_tree_oid":{"pattern":"^[0-9a-f]{40}$"},"expected_merge_commit_oid":{"pattern":"^[0-9a-f]{40}$"},"commit_message_utf8":{"type":"string"},"commit_message_sha256":{"pattern":"^[0-9a-f]{64}$"},"author_epoch":{"type":"integer","minimum":0},"committer_epoch":{"type":"integer","minimum":0},"author_timezone":{"pattern":"^[+-]((0\\d|1[0-3])[0-5]\\d|1400)$"},"committer_timezone":{"pattern":"^[+-]((0\\d|1[0-3])[0-5]\\d|1400)$"},"status_contexts":{"type":"array","minItems":5,"maxItems":5,"uniqueItems":true},"status_evidence_sha256":{"type":"object"},"issued_at":{"type":"string"},"expires_at":{"type":"string"},"grant_signer_identity":{"const":"aios-exact-two-parent-merge-grant-signer"},"grant_signature_namespace":{"const":"aios-exact-two-parent-merge-grant-v1"}}}
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "TASK_SCOPED_GOVERNANCE_BOOTSTRAP",
|
||||
"title": "TASK_SCOPED_GOVERNANCE_BOOTSTRAP observed approval envelope",
|
||||
"description": "Versioned fail-closed validation envelope assembled from an immutable request plus observed Owner, Git, machine-gate, runtime, and mirror evidence.",
|
||||
"x-schema-version": 1,
|
||||
"x-schema-status": "CANDIDATE",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"document_kind",
|
||||
"request_id",
|
||||
"schema_id",
|
||||
"schema_version",
|
||||
"request_created_at",
|
||||
"request_expires_at",
|
||||
"requester_identity",
|
||||
"repository_identity",
|
||||
"approval_plan_path",
|
||||
"approval_plan_blob_oid",
|
||||
"approval_plan_sha256",
|
||||
"schema_document_sha256",
|
||||
"validator_policy_sha256",
|
||||
"required_machine_contexts",
|
||||
"governance_base_ref",
|
||||
"governance_base_oid",
|
||||
"request_head_ref",
|
||||
"request_head_oid",
|
||||
"code_base_ref",
|
||||
"code_base_oid",
|
||||
"candidate_commit_oid",
|
||||
"candidate_parent_oid",
|
||||
"candidate_tree_oid",
|
||||
"candidate_diff_sha256",
|
||||
"candidate_files_sha256",
|
||||
"target_ref",
|
||||
"target_initial_state",
|
||||
"provision_publisher",
|
||||
"provision_writer",
|
||||
"provision_credential_projection",
|
||||
"publisher_identity",
|
||||
"writer_identity",
|
||||
"credential_secret_ref",
|
||||
"publisher_scope",
|
||||
"writer_scope",
|
||||
"credential_scope",
|
||||
"observed_preapproval_operations",
|
||||
"allowed_operations",
|
||||
"allowed_operations_sha256",
|
||||
"denied_operations",
|
||||
"maximum_operation_count",
|
||||
"maximum_pr_count",
|
||||
"maximum_branch_count",
|
||||
"maximum_commit_count",
|
||||
"allowed_paths",
|
||||
"allowed_paths_sha256",
|
||||
"approved_at",
|
||||
"expires_at",
|
||||
"maximum_lifetime_seconds",
|
||||
"auto_revoke",
|
||||
"required_human_approvals",
|
||||
"allowed_human_approvers",
|
||||
"approved_by",
|
||||
"owner_review_id",
|
||||
"owner_review_commit_oid",
|
||||
"approval_plan_id",
|
||||
"approval_audit_event_id",
|
||||
"production_runtime_anchor_change",
|
||||
"production_writes_enabled",
|
||||
"tag_operations",
|
||||
"registry_operations",
|
||||
"baseline_operations",
|
||||
"deployment_operations",
|
||||
"force_push_allowed",
|
||||
"tag_rewrite_allowed",
|
||||
"direct_main_push_allowed"
|
||||
],
|
||||
"properties": {
|
||||
"document_kind": {"const": "ENVELOPE"},
|
||||
"request_id": {"type": "string", "minLength": 3, "maxLength": 192, "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]+$"},
|
||||
"schema_id": {"const": "TASK_SCOPED_GOVERNANCE_BOOTSTRAP"},
|
||||
"schema_version": {"const": 1},
|
||||
"request_created_at": {"type": "string", "format": "date-time", "pattern": "Z$"},
|
||||
"request_expires_at": {"type": "string", "format": "date-time", "pattern": "Z$"},
|
||||
"requester_identity": {"$ref": "#/$defs/identity"},
|
||||
"repository_identity": {"const": "aios/aios"},
|
||||
"approval_plan_path": {"type": "string", "pattern": "^approvals/governance-bootstrap/[A-Za-z0-9._-]+\\.json$"},
|
||||
"approval_plan_blob_oid": {"$ref": "#/$defs/oid"},
|
||||
"approval_plan_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"schema_document_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"validator_policy_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"required_machine_contexts": {
|
||||
"type": "array",
|
||||
"minItems": 5,
|
||||
"maxItems": 5,
|
||||
"uniqueItems": true,
|
||||
"items": {"enum": [
|
||||
"governance/authority-mirror",
|
||||
"governance/fault-injection",
|
||||
"governance/runtime-isolation",
|
||||
"governance/schema-contract",
|
||||
"governance/validator-security"
|
||||
]}
|
||||
},
|
||||
"governance_base_ref": {"const": "refs/heads/governance/approvals"},
|
||||
"governance_base_oid": {"$ref": "#/$defs/oid"},
|
||||
"request_head_ref": {"type": "string", "pattern": "^refs/heads/governance/request/[A-Za-z0-9._/-]+$"},
|
||||
"request_head_oid": {"$ref": "#/$defs/oid"},
|
||||
"code_base_ref": {"type": "string", "pattern": "^refs/heads/patch/[A-Za-z0-9._/-]+$"},
|
||||
"code_base_oid": {"$ref": "#/$defs/oid"},
|
||||
"candidate_commit_oid": {"$ref": "#/$defs/oid"},
|
||||
"candidate_parent_oid": {"$ref": "#/$defs/oid"},
|
||||
"candidate_tree_oid": {"$ref": "#/$defs/oid"},
|
||||
"candidate_diff_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"candidate_files_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"target_ref": {"type": "string", "pattern": "^refs/heads/patch/[A-Za-z0-9._/-]+$"},
|
||||
"target_initial_state": {"const": "ABSENT"},
|
||||
"provision_publisher": {"const": true},
|
||||
"provision_writer": {"const": true},
|
||||
"provision_credential_projection": {"const": true},
|
||||
"publisher_identity": {"$ref": "#/$defs/identity"},
|
||||
"writer_identity": {"$ref": "#/$defs/identity"},
|
||||
"credential_secret_ref": {"type": "string", "pattern": "^(systemd-credential|secret-ref)://[A-Za-z0-9_.-]+$"},
|
||||
"publisher_scope": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["repository", "local_code_write", "administrator"],
|
||||
"properties": {
|
||||
"repository": {"const": "aios/aios"},
|
||||
"local_code_write": {"const": false},
|
||||
"administrator": {"const": false}
|
||||
}
|
||||
},
|
||||
"writer_scope": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["repository", "remote_credentials", "remote_push", "run_as_root"],
|
||||
"properties": {
|
||||
"repository": {"const": "aios/aios"},
|
||||
"remote_credentials": {"const": false},
|
||||
"remote_push": {"const": false},
|
||||
"run_as_root": {"const": false}
|
||||
}
|
||||
},
|
||||
"credential_scope": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"provider", "projection", "audience", "writer_visible",
|
||||
"runtime_anchor_visible", "expires_with_request", "revocation_required",
|
||||
"post_revoke_http_status"
|
||||
],
|
||||
"properties": {
|
||||
"provider": {"const": "systemd"},
|
||||
"projection": {"const": "LoadCredential"},
|
||||
"audience": {"$ref": "#/$defs/identity"},
|
||||
"writer_visible": {"const": false},
|
||||
"runtime_anchor_visible": {"const": false},
|
||||
"expires_with_request": {"const": true},
|
||||
"revocation_required": {"const": true},
|
||||
"post_revoke_http_status": {"const": 401}
|
||||
}
|
||||
},
|
||||
"observed_preapproval_operations": {
|
||||
"type": "array",
|
||||
"minItems": 2,
|
||||
"maxItems": 2,
|
||||
"uniqueItems": true,
|
||||
"items": {"enum": [
|
||||
"create_governance_request_branch",
|
||||
"create_governance_approval_pr"
|
||||
]}
|
||||
},
|
||||
"allowed_operations": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"uniqueItems": true,
|
||||
"items": {"enum": [
|
||||
"provision_task_publisher",
|
||||
"provision_task_writer",
|
||||
"provision_task_credential_projection",
|
||||
"create_approved_code_branch",
|
||||
"push_approved_code_branch",
|
||||
"read_remote_refs",
|
||||
"revoke_task_credentials",
|
||||
"remove_task_identities"
|
||||
]}
|
||||
},
|
||||
"allowed_operations_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"denied_operations": {"type": "array", "minItems": 12, "uniqueItems": true, "items": {"type": "string"}},
|
||||
"maximum_operation_count": {"type": "integer", "minimum": 1, "maximum": 10},
|
||||
"maximum_pr_count": {"const": 1},
|
||||
"maximum_branch_count": {"const": 1},
|
||||
"maximum_commit_count": {"const": 1},
|
||||
"allowed_paths": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "minLength": 1}},
|
||||
"allowed_paths_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"approved_at": {"type": "string", "format": "date-time", "pattern": "Z$"},
|
||||
"expires_at": {"type": "string", "format": "date-time", "pattern": "Z$"},
|
||||
"maximum_lifetime_seconds": {"type": "integer", "minimum": 1, "maximum": 43200},
|
||||
"auto_revoke": {"const": true},
|
||||
"required_human_approvals": {"const": 1},
|
||||
"allowed_human_approvers": {"const": ["owner"]},
|
||||
"approved_by": {"const": "owner"},
|
||||
"owner_review_id": {"type": "integer", "minimum": 1},
|
||||
"owner_review_commit_oid": {"$ref": "#/$defs/oid"},
|
||||
"approval_plan_id": {"type": "string", "minLength": 3, "maxLength": 192},
|
||||
"approval_audit_event_id": {"type": "string", "minLength": 3, "maxLength": 192},
|
||||
"production_runtime_anchor_change": {"const": false},
|
||||
"production_writes_enabled": {"const": false},
|
||||
"tag_operations": {"const": false},
|
||||
"registry_operations": {"const": false},
|
||||
"baseline_operations": {"const": false},
|
||||
"deployment_operations": {"const": false},
|
||||
"force_push_allowed": {"const": false},
|
||||
"tag_rewrite_allowed": {"const": false},
|
||||
"direct_main_push_allowed": {"const": false}
|
||||
},
|
||||
"$defs": {
|
||||
"plan": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"document_kind", "schema_id", "schema_version", "payload_sha256",
|
||||
"payload"
|
||||
],
|
||||
"properties": {
|
||||
"document_kind": {"const": "PLAN"},
|
||||
"schema_id": {"const": "TASK_SCOPED_GOVERNANCE_BOOTSTRAP_PLAN"},
|
||||
"schema_version": {"const": 1},
|
||||
"payload_sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
|
||||
"payload": {"$ref": "#/$defs/planPayload"}
|
||||
}
|
||||
},
|
||||
"planPayload": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"allowed_human_approvers", "allowed_operations", "allowed_operations_sha256",
|
||||
"allowed_paths", "allowed_paths_sha256", "approval_plan_id", "approval_plan_path",
|
||||
"approved_by", "auto_revoke", "baseline_operations", "candidate_commit_oid",
|
||||
"candidate_diff_sha256", "candidate_files_sha256", "candidate_parent_oid",
|
||||
"candidate_tree_oid", "code_base_oid", "code_base_ref", "credential_scope",
|
||||
"credential_secret_ref", "denied_operations", "deployment_operations",
|
||||
"direct_main_push_allowed", "expires_at", "force_push_allowed",
|
||||
"governance_base_oid", "governance_base_ref", "maximum_branch_count",
|
||||
"maximum_commit_count", "maximum_lifetime_seconds", "maximum_operation_count",
|
||||
"maximum_pr_count", "observed_preapproval_operations",
|
||||
"production_runtime_anchor_change", "production_writes_enabled",
|
||||
"provision_credential_projection", "provision_publisher", "provision_writer",
|
||||
"publisher_identity", "publisher_scope", "registry_operations",
|
||||
"repository_identity", "request_created_at", "request_expires_at",
|
||||
"request_head_ref", "request_id", "requester_identity",
|
||||
"required_human_approvals", "required_machine_contexts",
|
||||
"schema_document_sha256", "schema_id", "schema_version", "tag_operations",
|
||||
"tag_rewrite_allowed", "target_initial_state", "target_ref",
|
||||
"validator_policy_sha256", "writer_identity", "writer_scope"
|
||||
],
|
||||
"properties": {
|
||||
"allowed_human_approvers": {"$ref": "#/properties/allowed_human_approvers"},
|
||||
"allowed_operations": {"$ref": "#/properties/allowed_operations"},
|
||||
"allowed_operations_sha256": {"$ref": "#/properties/allowed_operations_sha256"},
|
||||
"allowed_paths": {"$ref": "#/properties/allowed_paths"},
|
||||
"allowed_paths_sha256": {"$ref": "#/properties/allowed_paths_sha256"},
|
||||
"approval_plan_id": {"$ref": "#/properties/approval_plan_id"},
|
||||
"approval_plan_path": {"$ref": "#/properties/approval_plan_path"},
|
||||
"approved_by": {"$ref": "#/properties/approved_by"},
|
||||
"auto_revoke": {"$ref": "#/properties/auto_revoke"},
|
||||
"baseline_operations": {"$ref": "#/properties/baseline_operations"},
|
||||
"candidate_commit_oid": {"$ref": "#/properties/candidate_commit_oid"},
|
||||
"candidate_diff_sha256": {"$ref": "#/properties/candidate_diff_sha256"},
|
||||
"candidate_files_sha256": {"$ref": "#/properties/candidate_files_sha256"},
|
||||
"candidate_parent_oid": {"$ref": "#/properties/candidate_parent_oid"},
|
||||
"candidate_tree_oid": {"$ref": "#/properties/candidate_tree_oid"},
|
||||
"code_base_oid": {"$ref": "#/properties/code_base_oid"},
|
||||
"code_base_ref": {"$ref": "#/properties/code_base_ref"},
|
||||
"credential_scope": {"$ref": "#/properties/credential_scope"},
|
||||
"credential_secret_ref": {"$ref": "#/properties/credential_secret_ref"},
|
||||
"denied_operations": {"$ref": "#/properties/denied_operations"},
|
||||
"deployment_operations": {"$ref": "#/properties/deployment_operations"},
|
||||
"direct_main_push_allowed": {"$ref": "#/properties/direct_main_push_allowed"},
|
||||
"expires_at": {"$ref": "#/properties/expires_at"},
|
||||
"force_push_allowed": {"$ref": "#/properties/force_push_allowed"},
|
||||
"governance_base_oid": {"$ref": "#/properties/governance_base_oid"},
|
||||
"governance_base_ref": {"$ref": "#/properties/governance_base_ref"},
|
||||
"maximum_branch_count": {"$ref": "#/properties/maximum_branch_count"},
|
||||
"maximum_commit_count": {"$ref": "#/properties/maximum_commit_count"},
|
||||
"maximum_lifetime_seconds": {"$ref": "#/properties/maximum_lifetime_seconds"},
|
||||
"maximum_operation_count": {"$ref": "#/properties/maximum_operation_count"},
|
||||
"maximum_pr_count": {"$ref": "#/properties/maximum_pr_count"},
|
||||
"observed_preapproval_operations": {"$ref": "#/properties/observed_preapproval_operations"},
|
||||
"production_runtime_anchor_change": {"$ref": "#/properties/production_runtime_anchor_change"},
|
||||
"production_writes_enabled": {"$ref": "#/properties/production_writes_enabled"},
|
||||
"provision_credential_projection": {"$ref": "#/properties/provision_credential_projection"},
|
||||
"provision_publisher": {"$ref": "#/properties/provision_publisher"},
|
||||
"provision_writer": {"$ref": "#/properties/provision_writer"},
|
||||
"publisher_identity": {"$ref": "#/properties/publisher_identity"},
|
||||
"publisher_scope": {"$ref": "#/properties/publisher_scope"},
|
||||
"registry_operations": {"$ref": "#/properties/registry_operations"},
|
||||
"repository_identity": {"$ref": "#/properties/repository_identity"},
|
||||
"request_created_at": {"$ref": "#/properties/request_created_at"},
|
||||
"request_expires_at": {"$ref": "#/properties/request_expires_at"},
|
||||
"request_head_ref": {"$ref": "#/properties/request_head_ref"},
|
||||
"request_id": {"$ref": "#/properties/request_id"},
|
||||
"requester_identity": {"$ref": "#/properties/requester_identity"},
|
||||
"required_human_approvals": {"$ref": "#/properties/required_human_approvals"},
|
||||
"required_machine_contexts": {"$ref": "#/properties/required_machine_contexts"},
|
||||
"schema_document_sha256": {"$ref": "#/properties/schema_document_sha256"},
|
||||
"schema_id": {"$ref": "#/properties/schema_id"},
|
||||
"schema_version": {"$ref": "#/properties/schema_version"},
|
||||
"tag_operations": {"$ref": "#/properties/tag_operations"},
|
||||
"tag_rewrite_allowed": {"$ref": "#/properties/tag_rewrite_allowed"},
|
||||
"target_initial_state": {"$ref": "#/properties/target_initial_state"},
|
||||
"target_ref": {"$ref": "#/properties/target_ref"},
|
||||
"validator_policy_sha256": {"$ref": "#/properties/validator_policy_sha256"},
|
||||
"writer_identity": {"$ref": "#/properties/writer_identity"},
|
||||
"writer_scope": {"$ref": "#/properties/writer_scope"}
|
||||
}
|
||||
},
|
||||
"oid": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
|
||||
"sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
|
||||
"identity": {"type": "string", "minLength": 3, "maxLength": 128, "pattern": "^[A-Za-z0-9][A-Za-z0-9_.-]+$"}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "AIOS_TASK_STATUS_MERGE_GRANT_V1",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"schema_id", "schema_version", "repository", "pr_number", "base_oid",
|
||||
"target_commit", "approved_tree_oid", "manifest_path", "manifest_blob_oid", "manifest_sha256",
|
||||
"payload_sha256", "status_identity", "merge_identity", "status_contexts",
|
||||
"status_write_quota", "merge_quota", "merge_method", "branch_push_allowed",
|
||||
"tag_write_allowed", "main_write_allowed", "registry_write_allowed",
|
||||
"baseline_write_allowed", "production_write_allowed", "issued_at", "expires_at",
|
||||
"audit_key_ref", "grant_signer_identity", "grant_signature_namespace",
|
||||
"status_evidence_sha256", "supersedes_pr_number", "supersedes_head_oid",
|
||||
"renewal_required", "timestamp_contract", "canonical_timezone",
|
||||
"raw_timestamp_preserved", "naive_datetime_allowed"
|
||||
],
|
||||
"properties": {
|
||||
"schema_id": {"const": "AIOS_TASK_STATUS_MERGE_GRANT"},
|
||||
"schema_version": {"const": 1},
|
||||
"repository": {"const": "aios/aios"},
|
||||
"pr_number": {"type": "integer", "minimum": 1},
|
||||
"base_oid": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
|
||||
"target_commit": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
|
||||
"approved_tree_oid": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
|
||||
"manifest_path": {"type": "string", "pattern": "^approvals/[A-Za-z0-9._/-]+\\.json$"},
|
||||
"manifest_blob_oid": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
|
||||
"manifest_sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
|
||||
"payload_sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
|
||||
"status_evidence_sha256": {
|
||||
"type": "object", "additionalProperties": false,
|
||||
"required": ["aios/branch-protection", "aios/evidence-closure", "aios/secret-scan", "aios/state-machine", "aios/validator"],
|
||||
"properties": {
|
||||
"aios/branch-protection": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
|
||||
"aios/evidence-closure": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
|
||||
"aios/secret-scan": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
|
||||
"aios/state-machine": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
|
||||
"aios/validator": {"type": "string", "pattern": "^[0-9a-f]{64}$"}
|
||||
}
|
||||
},
|
||||
"supersedes_pr_number": {"const": 36},
|
||||
"supersedes_head_oid": {"const": "a8644fc172bd8ebbba17a16e6bf586c93fd1e793"},
|
||||
"renewal_required": {"const": true},
|
||||
"timestamp_contract": {"const": "RFC3339_OFFSET_AWARE_V2"},
|
||||
"canonical_timezone": {"const": "UTC"},
|
||||
"raw_timestamp_preserved": {"const": true},
|
||||
"naive_datetime_allowed": {"const": false},
|
||||
"status_identity": {"enum": ["codex", "gpt"]},
|
||||
"merge_identity": {"enum": ["codex", "gpt"]},
|
||||
"status_contexts": {
|
||||
"type": "array", "minItems": 5, "maxItems": 5, "uniqueItems": true,
|
||||
"items": {"enum": ["aios/branch-protection", "aios/evidence-closure", "aios/secret-scan", "aios/state-machine", "aios/validator"]}
|
||||
},
|
||||
"status_write_quota": {"const": 5},
|
||||
"merge_quota": {"const": 1},
|
||||
"merge_method": {"const": "fast-forward-only"},
|
||||
"branch_push_allowed": {"const": false},
|
||||
"tag_write_allowed": {"const": false},
|
||||
"main_write_allowed": {"const": false},
|
||||
"registry_write_allowed": {"const": false},
|
||||
"baseline_write_allowed": {"const": false},
|
||||
"production_write_allowed": {"const": false},
|
||||
"issued_at": {"type": "string", "format": "date-time"},
|
||||
"expires_at": {"type": "string", "format": "date-time"},
|
||||
"audit_key_ref": {"type": "string", "pattern": "^systemd-credential://[A-Za-z0-9_.-]+$"},
|
||||
"grant_signer_identity": {"const": "aios-task-grant-signer"},
|
||||
"grant_signature_namespace": {"const": "aios-task-status-merge-grant-v1"}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(ROOT / "tools"))
|
||||
|
||||
from build_v5r1_package import ( # noqa: E402
|
||||
PackageClosureError,
|
||||
build_deterministic_zip,
|
||||
validate_package,
|
||||
write_manifest,
|
||||
)
|
||||
|
||||
|
||||
class V5R1PackageBuilderTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.temporary = tempfile.TemporaryDirectory()
|
||||
self.root = Path(self.temporary.name) / "AIOS-V2R1C4-TASK-SCOPED-RELEASE-V5R1"
|
||||
self.root.mkdir()
|
||||
(self.root / "payload.txt").write_text("fixed payload\n", encoding="utf-8", newline="\n")
|
||||
(self.root / "PACKAGE_COMPONENTS.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"package_zip_sha256_source": "EXTERNAL_SIDECAR",
|
||||
"wheel_sha256": "1" * 64,
|
||||
"deployment_definitions_sha256": "2" * 64,
|
||||
},
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
newline="\n",
|
||||
)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self.temporary.cleanup()
|
||||
|
||||
def test_two_independent_archives_are_byte_identical_and_sidecar_matches(self) -> None:
|
||||
write_manifest(self.root)
|
||||
first = self.root.parent / "first.zip"
|
||||
second = self.root.parent / "second.zip"
|
||||
first_sidecar = self.root.parent / "first-SHA256.txt"
|
||||
second_sidecar = self.root.parent / "second-SHA256.txt"
|
||||
first_digest = build_deterministic_zip(self.root, first, first_sidecar)
|
||||
second_digest = build_deterministic_zip(self.root, second, second_sidecar)
|
||||
self.assertEqual(first.read_bytes(), second.read_bytes())
|
||||
self.assertEqual(first_digest, hashlib.sha256(first.read_bytes()).hexdigest())
|
||||
self.assertEqual(second_digest, first_digest)
|
||||
self.assertEqual(first_sidecar.read_text(encoding="ascii"), f"{first_digest} first.zip\n")
|
||||
self.assertEqual(second_sidecar.read_text(encoding="ascii"), f"{first_digest} second.zip\n")
|
||||
|
||||
def test_manifest_covers_every_other_file_but_never_itself(self) -> None:
|
||||
write_manifest(self.root)
|
||||
lines = (self.root / "MANIFEST.sha256").read_text(encoding="ascii").splitlines()
|
||||
paths = [line.split(" ", 1)[1] for line in lines]
|
||||
self.assertEqual(paths, sorted(paths, key=lambda value: value.encode("utf-8")))
|
||||
self.assertEqual(paths, ["PACKAGE_COMPONENTS.json", "payload.txt"])
|
||||
self.assertNotIn("MANIFEST.sha256", paths)
|
||||
validate_package(self.root)
|
||||
|
||||
def test_formal_declarations_reject_self_reference_and_stale_placeholders(self) -> None:
|
||||
cases = [
|
||||
{"package_zip_sha256": "3" * 64},
|
||||
{"package_zip_sha256_source": "EXTERNAL_AFTER_PACKAGING"},
|
||||
{"package_zip_sha256_source": "EXTERNAL_SIDECAR", "wheel_sha256": "0" * 64},
|
||||
{"package_zip_sha256_source": "EXTERNAL_SIDECAR", "marker": "EXAMPLE-NOT-AUTHORIZATION"},
|
||||
]
|
||||
for replacement in cases:
|
||||
with self.subTest(replacement=replacement):
|
||||
document = dict(replacement)
|
||||
document.setdefault("wheel_sha256", "1" * 64)
|
||||
document.setdefault("deployment_definitions_sha256", "2" * 64)
|
||||
(self.root / "PACKAGE_COMPONENTS.json").write_text(
|
||||
json.dumps(document, sort_keys=True, separators=(",", ":")) + "\n",
|
||||
encoding="utf-8",
|
||||
newline="\n",
|
||||
)
|
||||
write_manifest(self.root)
|
||||
with self.assertRaises(PackageClosureError):
|
||||
validate_package(self.root)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,104 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT / "services" / "aios-single-owner-governance"))
|
||||
from aios_single_owner_governance.exact_two_parent_merge_broker import ( # noqa: E402
|
||||
AUTHORITY_REPO, GITEA_REPO, ContractDenied, ExactGrant, ExactTwoParentMergeBroker,
|
||||
)
|
||||
|
||||
OLD, HEAD, TREE = "a" * 40, "b" * 40, "c" * 40
|
||||
CONTEXTS = ["aios/branch-protection", "aios/evidence-closure", "aios/secret-scan", "aios/state-machine", "aios/validator"]
|
||||
|
||||
def grant() -> dict:
|
||||
now = datetime.now(timezone.utc).replace(microsecond=0)
|
||||
raw=b'{"approval_payload":{"phase":"B"},"other":true}\n'; raw_digest=hashlib.sha256(raw).hexdigest(); payload_digest=hashlib.sha256(b'{"phase":"B"}').hexdigest(); value = {"schema_id":"AIOS_EXACT_TWO_PARENT_MERGE_COMMIT_V1","schema_version":1,"repository":"aios/aios","target_ref":"refs/heads/governance/approvals","merge_mode":"EXACT_TWO_PARENT_COMMIT","operation_quota":1,"force":False,"pr_number":39,"expected_old_ref_oid":OLD,"approved_pr_head_oid":HEAD,"approved_pr_tree_oid":TREE,"first_parent_oid":OLD,"second_parent_oid":HEAD,"expected_merge_tree_oid":TREE,"expected_merge_commit_oid":"0"*40,"commit_message_utf8":"merge approved PR 39\n","commit_message_sha256":"","author_name":"AIOS","author_email":"aios@example.test","author_epoch":1,"author_timezone":"+0000","committer_name":"AIOS","committer_email":"aios@example.test","committer_epoch":1,"committer_timezone":"+0000","manifest_path":"approvals/manifest.json","manifest_blob_oid":"d"*40,"manifest_sha256":raw_digest,"payload_sha256":payload_digest,"status_identity":"codex","merge_identity":"gpt","status_contexts":CONTEXTS,"status_evidence_sha256":{c:"3"*64 for c in CONTEXTS},"issued_at":now.isoformat().replace("+00:00","Z"),"expires_at":(now+timedelta(hours=1)).isoformat().replace("+00:00","Z"),"audit_key_ref":"systemd-credential://audit","grant_signer_identity":"aios-exact-two-parent-merge-grant-signer","grant_signature_namespace":"aios-exact-two-parent-merge-grant-v1"}
|
||||
value["commit_message_sha256"] = hashlib.sha256(value["commit_message_utf8"].encode()).hexdigest()
|
||||
provisional = ExactGrant.from_dict(value)
|
||||
value["expected_merge_commit_oid"] = provisional.calculated_oid()
|
||||
return value
|
||||
|
||||
class API:
|
||||
def __init__(self): self.base, self.head, self.tree, self.review = OLD, HEAD, TREE, HEAD; self.statuses = True; self.branch = HEAD; self.review_time=None; self.status_rows=None; self.login="gpt"; self.state="open"; self.mergeable=True
|
||||
def get(self, path):
|
||||
if path == "/user": return {"login":self.login}
|
||||
if path.endswith("/reviews"): return [{"id":1,"state":"APPROVED","commit_id":self.review,"official":True,"stale":False,"dismissed":False,"submitted_at":self.review_time or datetime.now(timezone.utc).isoformat().replace("+00:00","Z"),"user":{"id":2,"login":"owner"}}]
|
||||
if "branches/" in path: return {"commit":{"id":self.branch}}
|
||||
if path.endswith("/statuses"): return self.status_rows if self.status_rows is not None else ([{"context":c,"status":"success","sha":HEAD,"creator":{"login":"codex"},"description":"x"+"3"*64,"created_at":"2026-01-01T00:00:00Z","id":1} for c in CONTEXTS] if self.statuses else [])
|
||||
return {"state":self.state,"mergeable":self.mergeable,"head":{"sha":self.head,"repo":{"tree_oid":self.tree}},"base":{"sha":self.base,"ref":"governance/approvals"}}
|
||||
|
||||
class Git:
|
||||
def __init__(self, oid): self.oid=oid; self.refs={GITEA_REPO:OLD,AUTHORITY_REPO:OLD}; self.parents=[]
|
||||
def commit_tree(self, q, tree, parents, message, env): self.tree_value=tree; self.parents=parents; self.message=message.decode(); self.env=env; return self.oid
|
||||
def verify_commit(self, repo, oid): return {"tree":self.tree_value,"parents":self.parents,"author":f"AIOS <aios@example.test> 1 +0000","committer":f"AIOS <aios@example.test> 1 +0000","message":self.message}
|
||||
def tree(self, repo, oid): return TREE
|
||||
def blob(self, repo, commit, path): return "d"*40
|
||||
def blob_data(self, repo, oid): return b'{"approval_payload":{"phase":"B"},"other":true}\n'
|
||||
def import_object(self, repo, source, oid): pass
|
||||
def fsck(self, repo, oid): pass
|
||||
def ref(self, repo, ref): return self.refs[repo]
|
||||
def update_ref(self, repo, ref, new, old):
|
||||
if self.refs[repo] != old: raise AssertionError("CAS")
|
||||
self.refs[repo]=new
|
||||
|
||||
class Tests(unittest.TestCase):
|
||||
def test_latest_status_window_manifest_branch_and_unknown_ref_fail_closed(self):
|
||||
item=ExactGrant.from_dict(grant())
|
||||
cases=[]
|
||||
api=API(); api.review_time=item.expires_at.isoformat().replace("+00:00","Z"); cases.append((api,Git(item.raw["expected_merge_commit_oid"])))
|
||||
api=API(); api.status_rows=[{"context":c,"status":"success","sha":HEAD,"creator":{"login":"codex"},"description":"bad","created_at":"2026-02-01T00:00:00Z","id":2} for c in CONTEXTS]; cases.append((api,Git(item.raw["expected_merge_commit_oid"])))
|
||||
api=API(); api.branch="f"*40; cases.append((api,Git(item.raw["expected_merge_commit_oid"])))
|
||||
api=API(); api.login="codex"; cases.append((api,Git(item.raw["expected_merge_commit_oid"])))
|
||||
api=API(); git=Git(item.raw["expected_merge_commit_oid"]); git.refs[GITEA_REPO]="e"*40; cases.append((api,git))
|
||||
for api, git in cases:
|
||||
with self.subTest(api=api), tempfile.TemporaryDirectory() as d, self.assertRaises(ContractDenied): ExactTwoParentMergeBroker(item,api,git,Path(d)/"j",b"audit").execute()
|
||||
def test_commit_bytes_fix_order_count_tree_message_and_identities(self):
|
||||
item=ExactGrant.from_dict(grant()); payload=item.commit_payload().decode()
|
||||
self.assertEqual(payload.split("\n")[:3],[f"tree {TREE}",f"parent {OLD}",f"parent {HEAD}"])
|
||||
self.assertEqual(payload.count("parent "),2)
|
||||
self.assertTrue(payload.endswith("merge approved PR 39\n"))
|
||||
for key, value in [("author_epoch",-1),("committer_epoch","1"),("author_email","x@@y"),("committer_name","x\r")]:
|
||||
changed=grant(); changed[key]=value
|
||||
with self.subTest(key=key), self.assertRaises(ContractDenied): ExactGrant.from_dict(changed)
|
||||
def test_fixed_fields_and_all_grant_negative_classes(self):
|
||||
for key, bad in [("first_parent_oid",HEAD),("second_parent_oid",OLD),("expected_merge_tree_oid",OLD),("target_ref","refs/heads/main"),("operation_quota",2),("commit_message_utf8","bad"),("author_timezone","UTC"),("author_name","bad\n"),("status_contexts",CONTEXTS[:4])]:
|
||||
v=grant(); v[key]=bad
|
||||
if key == "commit_message_utf8": v["commit_message_sha256"]=hashlib.sha256(bad.encode()).hexdigest()
|
||||
with self.subTest(key=key), self.assertRaises(ContractDenied): ExactGrant.from_dict(v)
|
||||
def test_expected_oid_and_preflight_negative_cases(self):
|
||||
v=grant(); v["expected_merge_commit_oid"]="f"*40
|
||||
with self.assertRaises(ContractDenied): ExactGrant.from_dict(v) if False else ExactTwoParentMergeBroker(ExactGrant.from_dict(v),API(),Git("f"*40),Path(tempfile.mktemp()),b"audit").execute()
|
||||
for attr, value in [("base","e"*40),("head","e"*40),("tree","e"*40),("review","e"*40),("statuses",False)]:
|
||||
item=ExactGrant.from_dict(grant()); api=API(); setattr(api,attr,value)
|
||||
with self.subTest(attr=attr), tempfile.TemporaryDirectory() as d, self.assertRaises(ContractDenied): ExactTwoParentMergeBroker(item,api,Git(item.raw["expected_merge_commit_oid"]),Path(d)/"j",b"audit").execute()
|
||||
def test_same_request_replays_and_different_request_fails(self):
|
||||
item=ExactGrant.from_dict(grant()); git=Git(item.raw["expected_merge_commit_oid"])
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
path=Path(d)/"j"; api=API(); api.branch=item.raw["expected_merge_commit_oid"]; broker=ExactTwoParentMergeBroker(item,api,git,path,b"audit"); first=broker.execute(); self.assertEqual(first,broker.execute())
|
||||
changed=grant(); changed["pr_number"]=40; changed["expected_merge_commit_oid"]=ExactGrant.from_dict(changed).calculated_oid()
|
||||
with self.assertRaisesRegex(ContractDenied,"JOURNAL_REQUEST_MISMATCH"): ExactTwoParentMergeBroker(ExactGrant.from_dict(changed),API(),Git(changed["expected_merge_commit_oid"]),path,b"audit").execute()
|
||||
def test_partial_old_new_reconciles_only_remaining_cas(self):
|
||||
item=ExactGrant.from_dict(grant()); git=Git(item.raw["expected_merge_commit_oid"]); git.refs[GITEA_REPO]=item.raw["expected_merge_commit_oid"]
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
api=API(); api.branch=item.raw["expected_merge_commit_oid"]; api.base=item.raw["expected_merge_commit_oid"]; api.state="closed"; api.mergeable=False
|
||||
self.assertEqual(ExactTwoParentMergeBroker(item,api,git,Path(d)/"j",b"audit").execute()["status"],"PASS")
|
||||
|
||||
def test_task_scoped_quarantine_and_explicit_cas_failure(self):
|
||||
item=ExactGrant.from_dict(grant())
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
root=Path(d)
|
||||
first=ExactTwoParentMergeBroker(item,API(),Git(item.raw["expected_merge_commit_oid"]),root/"j1",b"audit",root/"instance-a")
|
||||
second=ExactTwoParentMergeBroker(item,API(),Git(item.raw["expected_merge_commit_oid"]),root/"j2",b"audit",root/"instance-b")
|
||||
self.assertNotEqual(first.quarantine_root,second.quarantine_root)
|
||||
self.assertEqual(first.quarantine_root.name,item.digest)
|
||||
drift=Git(item.raw["expected_merge_commit_oid"]); drift.refs[AUTHORITY_REPO]="e"*40
|
||||
with self.assertRaisesRegex(ContractDenied,"GOVERNANCE_REF_COMPARE_AND_SWAP_FAILED"):
|
||||
ExactTwoParentMergeBroker(item,API(),drift,root/"j3",b"audit",root/"instance-c").execute()
|
||||
|
||||
if __name__ == "__main__": unittest.main()
|
||||
@@ -0,0 +1,81 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
import jsonschema
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
SCHEMA_PATH = (
|
||||
ROOT
|
||||
/ "services"
|
||||
/ "aios-single-owner-governance"
|
||||
/ "exact-two-parent-merge-closure-v1.schema.json"
|
||||
)
|
||||
|
||||
|
||||
def valid_closure() -> dict:
|
||||
old = "a" * 40
|
||||
head = "b" * 40
|
||||
tree = "c" * 40
|
||||
merge = "d" * 40
|
||||
return {
|
||||
"schema_id": "AIOS_EXACT_TWO_PARENT_MERGE_CLOSURE_V1",
|
||||
"schema_version": 1,
|
||||
"status": "PASS",
|
||||
"repository": "aios/aios",
|
||||
"target_ref": "refs/heads/governance/approvals",
|
||||
"merge_mode": "EXACT_TWO_PARENT_COMMIT",
|
||||
"force": False,
|
||||
"request_sha256": "1" * 64,
|
||||
"expected_old_ref_oid": old,
|
||||
"approved_pr_head_oid": head,
|
||||
"approved_pr_tree_oid": tree,
|
||||
"merge_commit_oid": merge,
|
||||
"parent_oids": [old, head],
|
||||
"tree_oid": tree,
|
||||
"commit_message_sha256": "2" * 64,
|
||||
"owner_review_exact_head": True,
|
||||
"required_statuses_exact_head": True,
|
||||
"quarantine_reproducibility": "PASS",
|
||||
"authority_fsck": "PASS",
|
||||
"gitea_fsck": "PASS",
|
||||
"authority_ref_oid": merge,
|
||||
"gitea_ref_oid": merge,
|
||||
"gitea_api_ref_oid": merge,
|
||||
"audit_chain_hash": "3" * 64,
|
||||
"audit_hmac_signature": "4" * 64,
|
||||
}
|
||||
|
||||
|
||||
class ExactTwoParentMergeClosureSchemaTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.schema = json.loads(SCHEMA_PATH.read_text(encoding="utf-8"))
|
||||
jsonschema.Draft202012Validator.check_schema(cls.schema)
|
||||
cls.validator = jsonschema.Draft202012Validator(cls.schema)
|
||||
|
||||
def test_valid_exact_closure_passes(self) -> None:
|
||||
self.validator.validate(valid_closure())
|
||||
|
||||
def test_shape_and_scope_expansion_fail_closed(self) -> None:
|
||||
mutations = {
|
||||
"one_parent": ("parent_oids", ["a" * 40]),
|
||||
"three_parents": ("parent_oids", ["a" * 40, "b" * 40, "e" * 40]),
|
||||
"main_ref": ("target_ref", "refs/heads/main"),
|
||||
"force": ("force", True),
|
||||
"unverified_owner": ("owner_review_exact_head", False),
|
||||
"unverified_status": ("required_statuses_exact_head", False),
|
||||
"failed_fsck": ("authority_fsck", "FAIL"),
|
||||
}
|
||||
for name, (field, value) in mutations.items():
|
||||
with self.subTest(name=name):
|
||||
document = valid_closure()
|
||||
document[field] = value
|
||||
self.assertTrue(list(self.validator.iter_errors(document)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,77 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT / "services" / "aios-single-owner-governance"))
|
||||
|
||||
from aios_single_owner_governance.exact_two_parent_merge_broker import SubprocessGit # noqa: E402
|
||||
|
||||
|
||||
@unittest.skipUnless(os.name == "posix" and Path("/usr/bin/git").is_file(), "POSIX Git integration")
|
||||
class ExactTwoParentMergePosixTests(unittest.TestCase):
|
||||
def _git(self, repository: Path, *arguments: str, payload: bytes | None = None, env=None) -> str:
|
||||
return subprocess.run(
|
||||
["/usr/bin/git", f"--git-dir={repository}", *arguments],
|
||||
input=payload,
|
||||
check=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
env=env,
|
||||
).stdout.decode().strip()
|
||||
|
||||
def test_real_git_build_import_fsck_and_exact_cas(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
gitea, authority = root / "gitea.git", root / "authority.git"
|
||||
subprocess.run(["/usr/bin/git", "init", "--bare", str(gitea)], check=True, stdout=subprocess.DEVNULL)
|
||||
subprocess.run(["/usr/bin/git", "init", "--bare", str(authority)], check=True, stdout=subprocess.DEVNULL)
|
||||
tree = self._git(gitea, "mktree", payload=b"")
|
||||
identity = {
|
||||
**os.environ,
|
||||
"GIT_AUTHOR_NAME": "AIOS Test",
|
||||
"GIT_AUTHOR_EMAIL": "test@aios.invalid",
|
||||
"GIT_AUTHOR_DATE": "1785542400 +0000",
|
||||
"GIT_COMMITTER_NAME": "AIOS Test",
|
||||
"GIT_COMMITTER_EMAIL": "test@aios.invalid",
|
||||
"GIT_COMMITTER_DATE": "1785542400 +0000",
|
||||
}
|
||||
base = self._git(gitea, "commit-tree", tree, payload=b"base\n", env=identity)
|
||||
head = self._git(gitea, "commit-tree", tree, "-p", base, payload=b"head\n", env=identity)
|
||||
for repository in (gitea, authority):
|
||||
if repository == authority:
|
||||
subprocess.run(
|
||||
["/usr/bin/git", f"--git-dir={repository}", "fetch", "--no-tags", "--no-write-fetch-head", f"file://{gitea}", head],
|
||||
check=True,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
self._git(repository, "update-ref", "refs/heads/governance/approvals", base)
|
||||
adapter = SubprocessGit(gitea_repo=str(gitea))
|
||||
fixed = {
|
||||
"GIT_AUTHOR_NAME": "AIOS Merge",
|
||||
"GIT_AUTHOR_EMAIL": "merge@aios.invalid",
|
||||
"GIT_AUTHOR_DATE": "1785546000 +0000",
|
||||
"GIT_COMMITTER_NAME": "AIOS Merge",
|
||||
"GIT_COMMITTER_EMAIL": "merge@aios.invalid",
|
||||
"GIT_COMMITTER_DATE": "1785546000 +0000",
|
||||
}
|
||||
first = adapter.commit_tree(root / "q1.git", tree, [base, head], b"exact merge\n", fixed)
|
||||
second = adapter.commit_tree(root / "q2.git", tree, [base, head], b"exact merge\n", fixed)
|
||||
self.assertEqual(first, second)
|
||||
self.assertEqual(adapter.verify_commit(str(root / "q1.git"), first)["parents"], [base, head])
|
||||
for repository in (gitea, authority):
|
||||
adapter.import_object(str(repository), root / "q1.git", first)
|
||||
adapter.fsck(str(repository), first)
|
||||
adapter.update_ref(str(repository), "refs/heads/governance/approvals", first, base)
|
||||
self.assertEqual(adapter.ref(str(repository), "refs/heads/governance/approvals"), first)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,39 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
UNIT = ROOT / "deploy" / "systemd" / "aios-exact-two-parent-merge-broker@.service"
|
||||
|
||||
|
||||
class ExactTwoParentMergeSystemdTests(unittest.TestCase):
|
||||
def test_unit_is_single_purpose_and_keeps_global_writes_disabled(self) -> None:
|
||||
raw = UNIT.read_text(encoding="utf-8")
|
||||
required = (
|
||||
"User=root",
|
||||
"Environment=AIOS_GITOPS_WRITES_ENABLED=false",
|
||||
"aios_single_owner_governance.exact_two_parent_merge_broker",
|
||||
"--grant /var/lib/aios-gitops/runtime/exact-two-parent-merge/%i/grant.json",
|
||||
"--grant-signature /var/lib/aios-gitops/runtime/exact-two-parent-merge/%i/grant.sig",
|
||||
"--journal /var/lib/aios-gitops/runtime/exact-two-parent-merge/%i/journal/journal.sqlite3",
|
||||
"--quarantine-root /run/aios-exact-two-parent-merge/%i",
|
||||
"ReadWritePaths=/data/aios-gitops/repo.git",
|
||||
"ReadWritePaths=/data/aios-gitops/runtime/gitea-bin/data/repositories/aios/aios.git",
|
||||
"ReadWritePaths=/run/aios-exact-two-parent-merge/%i",
|
||||
"IPAddressDeny=any",
|
||||
"IPAddressAllow=localhost",
|
||||
"NoNewPrivileges=true",
|
||||
)
|
||||
for value in required:
|
||||
with self.subTest(value=value):
|
||||
self.assertIn(value, raw)
|
||||
forbidden = ("AIOS_GITOPS_WRITES_ENABLED=true", "--force", "refs/heads/main", "refs/tags/")
|
||||
for value in forbidden:
|
||||
with self.subTest(forbidden=value):
|
||||
self.assertNotIn(value, raw)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,440 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
import urllib.error
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT / "services" / "aios-single-owner-governance"))
|
||||
|
||||
from aios_single_owner_governance.task_status_merge_broker import ( # noqa: E402
|
||||
ALLOWED_CONTEXTS,
|
||||
ContractDenied,
|
||||
MergeBroker,
|
||||
QuotaJournal,
|
||||
StatusBroker,
|
||||
TaskGrant,
|
||||
_time,
|
||||
_utc_timestamp,
|
||||
canonical,
|
||||
load_signed_task_grant,
|
||||
read_runtime_secret,
|
||||
)
|
||||
from aios_single_owner_governance.task_credential_broker import TaskCredentialBroker # noqa: E402
|
||||
from aios_single_owner_governance import task_credential_expiry_sweeper as sweeper # noqa: E402
|
||||
|
||||
|
||||
HEAD = "d" * 40
|
||||
OLD_HEAD = "a8644fc172bd8ebbba17a16e6bf586c93fd1e793"
|
||||
BASE = "8814dff20222625b60454c613b61ccf15fa20e51"
|
||||
|
||||
|
||||
def grant() -> dict:
|
||||
now = datetime.now(timezone.utc).replace(microsecond=0)
|
||||
value = {
|
||||
"schema_id": "AIOS_TASK_STATUS_MERGE_GRANT",
|
||||
"schema_version": 1,
|
||||
"repository": "aios/aios",
|
||||
"pr_number": 36,
|
||||
"base_oid": BASE,
|
||||
"target_commit": HEAD,
|
||||
"approved_tree_oid": "3649be2dac8fcea3a79cb1604b2cfb63f6cc0307",
|
||||
"manifest_path": "approvals/release-campaign/AIOS-V2R1C4-PHASE-B-RENEWAL.json",
|
||||
"manifest_blob_oid": "d6e4431992e4315799c7f0487f2b12a20d0e2890",
|
||||
"manifest_sha256": "2555c5e37f77f1d0aac51864733b55ee3e91169ae76bf67e8817de6d1874eea4",
|
||||
"payload_sha256": "4db7d3fd5aaa00a65eabfce7501f275b5f6c8a2587b6153fa8066ec0dafa5d6d",
|
||||
"status_evidence_sha256": {context: "1" * 64 for context in ALLOWED_CONTEXTS},
|
||||
"supersedes_pr_number": 36,
|
||||
"supersedes_head_oid": OLD_HEAD,
|
||||
"renewal_required": True,
|
||||
"timestamp_contract": "RFC3339_OFFSET_AWARE_V2",
|
||||
"canonical_timezone": "UTC",
|
||||
"raw_timestamp_preserved": True,
|
||||
"naive_datetime_allowed": False,
|
||||
"status_identity": "codex",
|
||||
"merge_identity": "codex",
|
||||
"status_contexts": sorted(ALLOWED_CONTEXTS),
|
||||
"status_write_quota": 5,
|
||||
"merge_quota": 1,
|
||||
"merge_method": "fast-forward-only",
|
||||
"branch_push_allowed": False,
|
||||
"tag_write_allowed": False,
|
||||
"main_write_allowed": False,
|
||||
"registry_write_allowed": False,
|
||||
"baseline_write_allowed": False,
|
||||
"production_write_allowed": False,
|
||||
"issued_at": now.isoformat().replace("+00:00", "Z"),
|
||||
"expires_at": (now + timedelta(hours=1)).isoformat().replace("+00:00", "Z"),
|
||||
"audit_key_ref": "systemd-credential://task-audit-hmac",
|
||||
"grant_signer_identity": "aios-task-grant-signer",
|
||||
"grant_signature_namespace": "aios-task-status-merge-grant-v1",
|
||||
}
|
||||
value["pr_number"] = 37
|
||||
return value
|
||||
|
||||
|
||||
class FakeAPI:
|
||||
def __init__(self, *, with_statuses: bool = False, fail_status_after: int | None = None,
|
||||
fail_after_merge: bool = False, review_timestamp: str | None = None,
|
||||
reviewer: str = "owner", review_commit: str = HEAD):
|
||||
self.posts: list[tuple[str, dict]] = []
|
||||
self.merged = False
|
||||
self.status_rows = [{"id": index + 1, "context": value, "status": "success", "sha": HEAD,
|
||||
"description": "task-scoped PASS evidence_sha256=" + "1" * 64,
|
||||
"creator": {"login": "codex"}} for index, value in enumerate(ALLOWED_CONTEXTS)] if with_statuses else []
|
||||
self.fail_status_after = fail_status_after
|
||||
self.fail_after_merge = fail_after_merge
|
||||
self.base = BASE
|
||||
self.review_timestamp = review_timestamp or datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
||||
self.reviewer = reviewer
|
||||
self.review_commit = review_commit
|
||||
|
||||
def get(self, path: str):
|
||||
if path.endswith("/reviews"):
|
||||
return [{
|
||||
"id": 76,
|
||||
"state": "APPROVED",
|
||||
"commit_id": self.review_commit,
|
||||
"official": True,
|
||||
"stale": False,
|
||||
"dismissed": False,
|
||||
"submitted_at": self.review_timestamp,
|
||||
"user": {"id": 2 if self.reviewer == "owner" else 42, "login": self.reviewer},
|
||||
}]
|
||||
if path.endswith("/statuses"):
|
||||
return list(self.status_rows)
|
||||
if path.endswith("/branches/governance%2Fapprovals"):
|
||||
return {"name": "governance/approvals", "commit": {"id": HEAD if self.merged else BASE}}
|
||||
if path.endswith("/pulls/37"):
|
||||
return {
|
||||
"number": 37,
|
||||
"state": "closed" if self.merged else "open",
|
||||
"merged": self.merged,
|
||||
"mergeable": True,
|
||||
"head": {"sha": HEAD},
|
||||
"base": {"sha": self.base, "ref": "governance/approvals"},
|
||||
"merge_commit_sha": HEAD if self.merged else None,
|
||||
}
|
||||
raise AssertionError(path)
|
||||
|
||||
def post(self, path: str, payload: dict):
|
||||
self.posts.append((path, payload))
|
||||
if path.endswith("/merge"):
|
||||
assert payload == {
|
||||
"Do": "fast-forward-only",
|
||||
"head_commit_id": HEAD,
|
||||
"force_merge": False,
|
||||
"delete_branch_after_merge": False,
|
||||
}
|
||||
self.merged = True
|
||||
if self.fail_after_merge:
|
||||
raise ContractDenied("SIMULATED_POST_MERGE_LOSS")
|
||||
return {"merged": True}
|
||||
if self.fail_status_after is not None and len(self.status_rows) >= self.fail_status_after:
|
||||
raise ContractDenied("SIMULATED_STATUS_FAILURE")
|
||||
row = {"id": len(self.posts), "context": payload["context"], "status": "success", "sha": HEAD,
|
||||
"description": payload["description"],
|
||||
"creator": {"login": "codex"}}
|
||||
self.status_rows.append(row)
|
||||
return row
|
||||
|
||||
|
||||
def evidence() -> list[dict]:
|
||||
return [{"context": value, "evidence_sha256": "1" * 64} for value in sorted(ALLOWED_CONTEXTS)]
|
||||
|
||||
|
||||
class TaskStatusMergeBrokerTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temporary = tempfile.TemporaryDirectory()
|
||||
self.root = Path(self.temporary.name)
|
||||
self.journals: list[QuotaJournal] = []
|
||||
|
||||
def tearDown(self):
|
||||
for journal in self.journals:
|
||||
journal.close()
|
||||
self.temporary.cleanup()
|
||||
|
||||
def journal(self, item: TaskGrant) -> QuotaJournal:
|
||||
value = QuotaJournal(self.root / "journal.sqlite3", item)
|
||||
self.journals.append(value)
|
||||
return value
|
||||
|
||||
def test_status_broker_publishes_exact_five_and_quota_is_single_use(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
api = FakeAPI()
|
||||
journal = self.journal(item)
|
||||
receipt = StatusBroker(item, journal, api, b"audit-key").publish(evidence())
|
||||
self.assertEqual(receipt["status"], "PASS")
|
||||
self.assertEqual(receipt["published_count"], 5)
|
||||
self.assertEqual({entry[1]["context"] for entry in api.posts}, ALLOWED_CONTEXTS)
|
||||
with self.assertRaisesRegex(ContractDenied, "STATUS_QUOTA_EXHAUSTED"):
|
||||
StatusBroker(item, journal, api, b"audit-key").publish(evidence())
|
||||
|
||||
def test_rfc3339_offset_parser_normalizes_to_utc(self):
|
||||
cases = {
|
||||
"2026-08-01T02:03:02Z": "2026-08-01T02:03:02Z",
|
||||
"2026-08-01T10:03:02+08:00": "2026-08-01T02:03:02Z",
|
||||
"2026-07-31T18:03:02-08:00": "2026-08-01T02:03:02Z",
|
||||
"2026-08-01T10:03:02.123456+08:00": "2026-08-01T02:03:02.123456Z",
|
||||
}
|
||||
for raw, expected in cases.items():
|
||||
with self.subTest(raw=raw):
|
||||
self.assertEqual(_utc_timestamp(_time(raw)), expected)
|
||||
|
||||
def test_rfc3339_offset_parser_rejects_ambiguous_or_invalid_values(self):
|
||||
invalid = [
|
||||
"2026-08-01T10:03:02", "2026-08-01", "2026-08-01T10:03:02+0800",
|
||||
"2026-08-01T10:03:02+25:00", "2026-08-01T10:03:02z",
|
||||
" 2026-08-01T10:03:02+08:00", "2026-08-01T10:03:02+08:00 ",
|
||||
None, 1, [],
|
||||
]
|
||||
for raw in invalid:
|
||||
with self.subTest(raw=raw):
|
||||
with self.assertRaisesRegex(ContractDenied, "TIMESTAMP_INVALID"):
|
||||
_time(raw) # type: ignore[arg-type]
|
||||
|
||||
def test_offset_review_preserves_raw_and_canonical_utc_in_receipt(self):
|
||||
value = grant()
|
||||
value["issued_at"] = "2026-08-01T00:00:00Z"
|
||||
value["expires_at"] = "2026-08-01T12:00:00Z"
|
||||
item = TaskGrant.from_dict(value, now=datetime(2026, 8, 1, 3, tzinfo=timezone.utc))
|
||||
api = FakeAPI(review_timestamp="2026-08-01T10:03:02+08:00")
|
||||
receipt = StatusBroker(item, self.journal(item), api, b"audit-key").publish(evidence())
|
||||
self.assertEqual(receipt["review_timestamp_raw"], "2026-08-01T10:03:02+08:00")
|
||||
self.assertEqual(receipt["review_timestamp_utc"], "2026-08-01T02:03:02Z")
|
||||
|
||||
def test_review_expiry_boundary_is_fail_closed(self):
|
||||
value = grant()
|
||||
value["issued_at"] = "2026-08-01T00:00:00Z"
|
||||
value["expires_at"] = "2026-08-01T02:03:02Z"
|
||||
item = TaskGrant.from_dict(value, now=datetime(2026, 8, 1, 1, tzinfo=timezone.utc))
|
||||
for raw in ("2026-08-01T02:03:02Z", "2026-08-01T02:03:03Z"):
|
||||
with self.subTest(raw=raw), self.assertRaisesRegex(ContractDenied, "OWNER_APPROVAL_EXPIRED"):
|
||||
StatusBroker(item, self.journal(item), FakeAPI(review_timestamp=raw), b"audit-key").publish(evidence())
|
||||
|
||||
def test_wrong_reviewer_and_reviewed_head_remain_rejected(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
for api in (FakeAPI(reviewer="codex"), FakeAPI(review_commit="e" * 40)):
|
||||
with self.assertRaisesRegex(ContractDenied, "OWNER_EXACT_HEAD_APPROVAL_MISSING"):
|
||||
StatusBroker(item, self.journal(item), api, b"audit-key").publish(evidence())
|
||||
|
||||
def test_merge_broker_uses_fast_forward_only_and_binds_exact_head(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
api = FakeAPI(with_statuses=True)
|
||||
journal = self.journal(item)
|
||||
receipt = MergeBroker(item, journal, api, b"audit-key").merge()
|
||||
self.assertEqual(receipt["status"], "PASS")
|
||||
self.assertEqual(receipt["governance_oid"], HEAD)
|
||||
self.assertTrue(api.merged)
|
||||
with self.assertRaisesRegex(ContractDenied, "MERGE_QUOTA_EXHAUSTED"):
|
||||
MergeBroker(item, journal, FakeAPI(with_statuses=True), b"audit-key").merge()
|
||||
|
||||
def test_contract_rejects_scope_expansion_secret_and_wrong_identity(self):
|
||||
value = grant()
|
||||
value["main_write_allowed"] = True
|
||||
with self.assertRaisesRegex(ContractDenied, "WRITE_SCOPE_EXPANDED"):
|
||||
TaskGrant.from_dict(value)
|
||||
|
||||
def test_unbound_evidence_fails_closed(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
changed = evidence()
|
||||
changed[0]["evidence_sha256"] = "2" * 64
|
||||
with self.assertRaisesRegex(ContractDenied, "EVIDENCE_SET_INVALID"):
|
||||
StatusBroker(item, self.journal(item), FakeAPI(), b"audit-key").publish(changed)
|
||||
|
||||
def test_grant_requires_detached_asymmetric_signature(self):
|
||||
grant_path = self.root / "grant.json"
|
||||
signature_path = self.root / "grant.sig"
|
||||
allowed_signers = self.root / "allowed_signers"
|
||||
raw = canonical(grant()) + b"\n"
|
||||
grant_path.write_bytes(raw)
|
||||
signature_path.write_text("ssh-signature", encoding="utf-8")
|
||||
allowed_signers.write_text("aios-task-grant-signer ssh-ed25519 AAAA", encoding="utf-8")
|
||||
with mock.patch("aios_single_owner_governance.task_status_merge_broker.subprocess.run") as runner:
|
||||
item = load_signed_task_grant(grant_path, signature_path, allowed_signers)
|
||||
self.assertEqual(item.raw["pr_number"], 37)
|
||||
self.assertEqual(runner.call_args.kwargs["input"], raw)
|
||||
with mock.patch(
|
||||
"aios_single_owner_governance.task_status_merge_broker.subprocess.run",
|
||||
side_effect=subprocess.CalledProcessError(255, ["ssh-keygen"]),
|
||||
):
|
||||
with self.assertRaisesRegex(ContractDenied, "GRANT_SIGNATURE_INVALID"):
|
||||
load_signed_task_grant(grant_path, signature_path, allowed_signers)
|
||||
|
||||
def test_base_movement_blocks_status_publication_before_side_effect(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
api = FakeAPI()
|
||||
api.base = "c" * 40
|
||||
with self.assertRaisesRegex(ContractDenied, "PR_BINDING_INVALID"):
|
||||
StatusBroker(item, self.journal(item), api, b"audit-key").publish(evidence())
|
||||
self.assertEqual(api.posts, [])
|
||||
|
||||
def test_credential_provision_rechecks_local_owner_and_refs_before_issuance(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
broker = TaskCredentialBroker(item, self.root / "audit", b"audit-key")
|
||||
with mock.patch("aios_single_owner_governance.task_credential_broker.os.geteuid", return_value=0, create=True), \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker._safe_absolute"), \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker._verify_local_governance_binding", side_effect=ContractDenied("LOCAL_OWNER_APPROVAL_MISSING")), \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker.subprocess.run") as runner:
|
||||
with self.assertRaisesRegex(ContractDenied, "LOCAL_OWNER_APPROVAL_MISSING"):
|
||||
broker.provision(
|
||||
identity="codex",
|
||||
token_name="pr37-dddddddddddd-status",
|
||||
encrypted_path=Path("/run/aios-task-credential-broker/status.cred"),
|
||||
)
|
||||
runner.assert_not_called()
|
||||
value = grant()
|
||||
value["status_identity"] = "gitops-bot"
|
||||
with self.assertRaisesRegex(ContractDenied, "IDENTITY_NOT_ALLOWED"):
|
||||
TaskGrant.from_dict(value)
|
||||
|
||||
def test_systemd_credential_projection_mode_and_name_are_supported_exactly(self):
|
||||
metadata = mock.Mock(st_mode=0o100440, st_uid=0, st_gid=0)
|
||||
credential = Path("/run/credentials/unit/gitea-token")
|
||||
with mock.patch.dict("os.environ", {"CREDENTIALS_DIRECTORY": str(credential.parent)}), \
|
||||
mock.patch("aios_single_owner_governance.task_status_merge_broker.os.open", return_value=7), \
|
||||
mock.patch("aios_single_owner_governance.task_status_merge_broker.os.fstat", return_value=metadata), \
|
||||
mock.patch("aios_single_owner_governance.task_status_merge_broker.os.read", return_value=b"x" * 40), \
|
||||
mock.patch("aios_single_owner_governance.task_status_merge_broker.os.close"):
|
||||
self.assertEqual(read_runtime_secret(credential), b"x" * 40)
|
||||
item = TaskGrant.from_dict(grant())
|
||||
broker = TaskCredentialBroker(item, self.root / "provision-audit", b"audit-key")
|
||||
connection = mock.MagicMock()
|
||||
connection.execute.return_value.fetchall.return_value = [(101, 42)]
|
||||
issued = mock.Mock(stdout=b"x" * 40)
|
||||
encrypted = mock.Mock(stdout=b"")
|
||||
with mock.patch("aios_single_owner_governance.task_credential_broker.os.geteuid", return_value=0, create=True), \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker._safe_absolute"), \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker._verify_local_governance_binding"), \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker.os.chmod"), \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker.subprocess.run", side_effect=[issued, encrypted]) as runner, \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker.sqlite3.connect", return_value=connection):
|
||||
broker.provision(identity="codex", token_name="pr37-dddddddddddd-status", encrypted_path=Path("/run/aios-task-credential-broker/status.cred"))
|
||||
self.assertIn("--name=gitea-token", runner.call_args_list[1].args[0])
|
||||
|
||||
def test_expiry_sweeper_revokes_projected_token_after_grant_expiry(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
runtime_root = self.root / "runtime"
|
||||
credential_root = self.root / "credentials"
|
||||
task_dir = runtime_root / "task-37"
|
||||
audit_dir = task_dir / "audit"
|
||||
audit_dir.mkdir(parents=True)
|
||||
credential_root.mkdir()
|
||||
(task_dir / "grant.json").write_text("{}", encoding="utf-8")
|
||||
(task_dir / "grant.sig").write_text("signature", encoding="utf-8")
|
||||
token_name = "pr37-dddddddddddd-status"
|
||||
encrypted_path = credential_root / "status.cred"
|
||||
encrypted_path.write_text("encrypted", encoding="utf-8")
|
||||
(credential_root / "task-37-audit.cred").write_text("encrypted-audit-key", encoding="utf-8")
|
||||
real_broker = TaskCredentialBroker(item, audit_dir, b"audit-key")
|
||||
receipt = real_broker._audit({
|
||||
"status": "PASS", "operation": "credential_provision", "identity": "codex",
|
||||
"token_name": token_name, "token_id": 101, "token_uid": 7,
|
||||
"grant_sha256": item.digest,
|
||||
"credential_ref": "systemd-credential-encrypted://status.cred",
|
||||
})
|
||||
real_broker.validate_provision_receipt(receipt, encrypted_path)
|
||||
tampered = dict(receipt)
|
||||
tampered["token_id"] = 102
|
||||
with self.assertRaisesRegex(ContractDenied, "PROVISION_RECEIPT_INVALID"):
|
||||
real_broker.validate_provision_receipt(tampered, encrypted_path)
|
||||
fake_broker = mock.Mock()
|
||||
fake_broker.revoke.return_value = {"status": "PASS", "old_token_http_status": 401}
|
||||
with mock.patch.object(sweeper, "RUNTIME_ROOT", runtime_root), \
|
||||
mock.patch.object(sweeper, "CREDENTIAL_ROOT", credential_root), \
|
||||
mock.patch.object(sweeper.os, "geteuid", return_value=0, create=True), \
|
||||
mock.patch.object(sweeper, "load_signed_task_grant", return_value=item), \
|
||||
mock.patch.object(sweeper.subprocess, "run", return_value=mock.Mock(stdout=b"audit-key")), \
|
||||
mock.patch.object(sweeper, "TaskCredentialBroker", return_value=fake_broker) as broker_type:
|
||||
results = sweeper.sweep(runtime_root, self.root / "allowed_signers", now=item.expires_at)
|
||||
self.assertEqual(results, [{"status": "PASS", "old_token_http_status": 401}])
|
||||
broker_type.assert_called_once_with(item, audit_dir, b"audit-key")
|
||||
fake_broker.revoke.assert_called_once()
|
||||
|
||||
def test_revocation_is_exact_access_token_row_only_and_proves_http_401(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
broker = TaskCredentialBroker(item, self.root / "revoke-audit", b"audit-key")
|
||||
token_name = "pr37-dddddddddddd-status"
|
||||
receipt = broker._audit({
|
||||
"status": "PASS", "operation": "credential_provision", "identity": "codex",
|
||||
"token_name": token_name, "token_id": 101, "token_uid": 7,
|
||||
"grant_sha256": item.digest,
|
||||
"credential_ref": "systemd-credential-encrypted://status.cred",
|
||||
})
|
||||
response = mock.MagicMock()
|
||||
response.__enter__.return_value.status = 200
|
||||
unauthorized = urllib.error.HTTPError("http://127.0.0.1:3000/api/v1/repos/aios/aios", 401, "Unauthorized", {}, None)
|
||||
write_connection = mock.MagicMock()
|
||||
write_connection.execute.return_value.rowcount = 1
|
||||
read_connection = mock.MagicMock()
|
||||
read_connection.execute.return_value.fetchone.return_value = (0,)
|
||||
with mock.patch("aios_single_owner_governance.task_credential_broker.os.geteuid", return_value=0, create=True), \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker._safe_absolute"), \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker.subprocess.run", return_value=mock.Mock(stdout=b"x" * 40)), \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker.sqlite3.connect", side_effect=[write_connection, read_connection]) as connect, \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker.urllib.request.urlopen", side_effect=[response, unauthorized]) as urlopen:
|
||||
result = broker.revoke(receipt=receipt, encrypted_path=Path("/run/aios-task-credential-broker/status.cred"))
|
||||
self.assertEqual(result["old_token_http_status"], 401)
|
||||
self.assertIn("/repos/aios/aios", urlopen.call_args_list[0].args[0].full_url)
|
||||
delete_sql = write_connection.execute.call_args_list[1].args[0]
|
||||
self.assertIn("DELETE FROM access_token", delete_sql)
|
||||
self.assertNotIn("commit_status", delete_sql)
|
||||
self.assertIn("mode=ro", connect.call_args_list[1].args[0])
|
||||
value = grant()
|
||||
value["token"] = "plaintext"
|
||||
with self.assertRaisesRegex(ContractDenied, "GRANT_FIELDS_INVALID"):
|
||||
TaskGrant.from_dict(value)
|
||||
|
||||
def test_audit_chain_is_content_addressed_and_contains_no_secret(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
journal = self.journal(item)
|
||||
receipt = StatusBroker(item, journal, FakeAPI(), b"audit-key").publish(evidence())
|
||||
serialized = json.dumps(receipt, sort_keys=True)
|
||||
self.assertNotIn("audit-key", serialized)
|
||||
self.assertEqual(len(receipt["audit_signature"]), 64)
|
||||
self.assertTrue(journal.verify_chain())
|
||||
|
||||
def test_credential_broker_is_root_only_and_never_returns_a_token(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
broker = TaskCredentialBroker(item, self.root / "audit", b"audit-key")
|
||||
with mock.patch("aios_single_owner_governance.task_credential_broker.os.geteuid", return_value=1000, create=True):
|
||||
with self.assertRaisesRegex(ContractDenied, "ROOT_BROKER_REQUIRED"):
|
||||
broker.provision(
|
||||
identity="codex",
|
||||
token_name="pr36-a8644fc172bd-status",
|
||||
encrypted_path=Path("/run/aios-task-credential-broker/status.cred"),
|
||||
)
|
||||
|
||||
def test_partial_status_side_effects_are_reconciled_without_duplicate_writes(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
api = FakeAPI(fail_status_after=2)
|
||||
journal = self.journal(item)
|
||||
with self.assertRaisesRegex(ContractDenied, "SIMULATED_STATUS_FAILURE"):
|
||||
StatusBroker(item, journal, api, b"audit-key").publish(evidence())
|
||||
self.assertEqual(len(api.status_rows), 2)
|
||||
api.fail_status_after = None
|
||||
receipt = StatusBroker(item, journal, api, b"audit-key").publish(evidence())
|
||||
self.assertEqual(receipt["published_count"], 5)
|
||||
self.assertEqual(len(api.status_rows), 5)
|
||||
|
||||
def test_post_merge_response_loss_is_reconciled_from_exact_remote_ref(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
api = FakeAPI(with_statuses=True, fail_after_merge=True)
|
||||
journal = self.journal(item)
|
||||
with self.assertRaisesRegex(ContractDenied, "SIMULATED_POST_MERGE_LOSS"):
|
||||
MergeBroker(item, journal, api, b"audit-key").merge()
|
||||
api.fail_after_merge = False
|
||||
receipt = MergeBroker(item, journal, api, b"audit-key").merge()
|
||||
self.assertTrue(receipt["reconciled"])
|
||||
self.assertEqual(receipt["governance_oid"], HEAD)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,206 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path, PurePosixPath
|
||||
import re
|
||||
import stat
|
||||
import zipfile
|
||||
|
||||
|
||||
SHA256 = re.compile(r"^[0-9a-f]{64}$")
|
||||
MANIFEST_LINE = re.compile(r"^([0-9a-f]{64}) ([^\\\r\n]+)$")
|
||||
FORBIDDEN_EXACT_VALUES = frozenset({
|
||||
"EXTERNAL_AFTER_PACKAGING",
|
||||
"EXAMPLE-NOT-AUTHORIZATION",
|
||||
"cab17cf731078d5771091f3d4f9507aaaea8988a9b5e77dd993afc93dbaed583",
|
||||
"163b6c62b895345c184c283fe0a9980358f19555da41f1f8d8b1b225e8492e5f",
|
||||
"ae4e85261b0e7b2c04474322f29a47e57e61bee5795a9f616e578aa938f92ac0",
|
||||
"1903664e58b5122a21c507a696a0f52f5a62c5096db1406e3003decbd788a531",
|
||||
"e5be655427f194992e9e434714e46b0abd7a1d7aca85eb577c791888314da915",
|
||||
"c5da5e1b089e0efa2021c7051fbeb6bd1fc5ff85c27d59db435fd61a270aaf09",
|
||||
"0" * 64,
|
||||
"9" * 64,
|
||||
})
|
||||
FORMAL_DECLARATIONS = (
|
||||
"PACKAGE_COMPONENTS.json",
|
||||
"PACKAGE_AUDIT.json",
|
||||
"OFFLINE_TEST_REPORT.json",
|
||||
)
|
||||
|
||||
|
||||
class PackageClosureError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def _relative(path: Path, root: Path) -> str:
|
||||
value = path.relative_to(root).as_posix()
|
||||
parsed = PurePosixPath(value)
|
||||
if parsed.is_absolute() or any(part in {"", ".", ".."} for part in parsed.parts):
|
||||
raise PackageClosureError("PACKAGE_PATH_INVALID")
|
||||
return value
|
||||
|
||||
|
||||
def _fixed_files(root: Path, *, include_manifest: bool) -> list[tuple[str, Path]]:
|
||||
root = root.resolve(strict=True)
|
||||
if root.is_symlink() or not root.is_dir():
|
||||
raise PackageClosureError("PACKAGE_ROOT_UNSAFE")
|
||||
result: list[tuple[str, Path]] = []
|
||||
for current, directories, files in os.walk(root, topdown=True, followlinks=False):
|
||||
current_path = Path(current)
|
||||
for name in list(directories):
|
||||
candidate = current_path / name
|
||||
metadata = candidate.lstat()
|
||||
if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode):
|
||||
raise PackageClosureError("PACKAGE_SPECIAL_FILE_PRESENT")
|
||||
for name in files:
|
||||
candidate = current_path / name
|
||||
metadata = candidate.lstat()
|
||||
if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISREG(metadata.st_mode):
|
||||
raise PackageClosureError("PACKAGE_SPECIAL_FILE_PRESENT")
|
||||
relative = _relative(candidate, root)
|
||||
if relative == "MANIFEST.sha256" and not include_manifest:
|
||||
continue
|
||||
result.append((relative, candidate))
|
||||
result.sort(key=lambda item: item[0].encode("utf-8"))
|
||||
if len({name for name, _ in result}) != len(result):
|
||||
raise PackageClosureError("PACKAGE_PATH_DUPLICATE")
|
||||
return result
|
||||
|
||||
|
||||
def write_manifest(root: Path) -> str:
|
||||
root = Path(root)
|
||||
entries = _fixed_files(root, include_manifest=False)
|
||||
lines = [f"{hashlib.sha256(path.read_bytes()).hexdigest()} {name}\n" for name, path in entries]
|
||||
raw = "".join(lines).encode("ascii")
|
||||
(root / "MANIFEST.sha256").write_bytes(raw)
|
||||
return hashlib.sha256(raw).hexdigest()
|
||||
|
||||
|
||||
def _walk_values(value: object):
|
||||
if isinstance(value, dict):
|
||||
for key, child in value.items():
|
||||
yield str(key)
|
||||
yield from _walk_values(child)
|
||||
elif isinstance(value, list):
|
||||
for child in value:
|
||||
yield from _walk_values(child)
|
||||
elif isinstance(value, str):
|
||||
yield value
|
||||
|
||||
|
||||
def _validate_declarations(root: Path) -> None:
|
||||
components_path = root / "PACKAGE_COMPONENTS.json"
|
||||
try:
|
||||
components = json.loads(components_path.read_text(encoding="utf-8"))
|
||||
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
|
||||
raise PackageClosureError("PACKAGE_COMPONENTS_INVALID") from exc
|
||||
if not isinstance(components, dict):
|
||||
raise PackageClosureError("PACKAGE_COMPONENTS_INVALID")
|
||||
if "package_zip_sha256" in components:
|
||||
raise PackageClosureError("ZIP_DIGEST_SELF_REFERENCE_FORBIDDEN")
|
||||
if components.get("package_zip_sha256_source") != "EXTERNAL_SIDECAR":
|
||||
raise PackageClosureError("ZIP_DIGEST_SOURCE_INVALID")
|
||||
for field in ("wheel_sha256", "deployment_definitions_sha256"):
|
||||
value = components.get(field)
|
||||
if not isinstance(value, str) or not SHA256.fullmatch(value):
|
||||
raise PackageClosureError("COMPONENT_DIGEST_INVALID")
|
||||
for name in FORMAL_DECLARATIONS:
|
||||
path = root / name
|
||||
if not path.is_file():
|
||||
if name == "PACKAGE_COMPONENTS.json":
|
||||
raise PackageClosureError("FORMAL_DECLARATION_MISSING")
|
||||
continue
|
||||
try:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
|
||||
raise PackageClosureError("FORMAL_DECLARATION_INVALID") from exc
|
||||
if any(item in FORBIDDEN_EXACT_VALUES for item in _walk_values(value)):
|
||||
raise PackageClosureError("STALE_OR_PLACEHOLDER_DECLARATION")
|
||||
|
||||
|
||||
def validate_package(root: Path) -> dict[str, str | int]:
|
||||
root = Path(root)
|
||||
_validate_declarations(root)
|
||||
files = _fixed_files(root, include_manifest=False)
|
||||
manifest_path = root / "MANIFEST.sha256"
|
||||
try:
|
||||
lines = manifest_path.read_text(encoding="ascii").splitlines()
|
||||
except (OSError, UnicodeDecodeError) as exc:
|
||||
raise PackageClosureError("MANIFEST_INVALID") from exc
|
||||
observed: dict[str, str] = {}
|
||||
for line in lines:
|
||||
match = MANIFEST_LINE.fullmatch(line)
|
||||
if match is None:
|
||||
raise PackageClosureError("MANIFEST_INVALID")
|
||||
digest, name = match.groups()
|
||||
parsed = PurePosixPath(name)
|
||||
if parsed.is_absolute() or any(part in {"", ".", ".."} for part in parsed.parts):
|
||||
raise PackageClosureError("MANIFEST_PATH_INVALID")
|
||||
if name in observed or name == "MANIFEST.sha256":
|
||||
raise PackageClosureError("MANIFEST_PATH_DUPLICATE")
|
||||
observed[name] = digest
|
||||
expected_names = [name for name, _ in files]
|
||||
if list(observed) != expected_names:
|
||||
raise PackageClosureError("MANIFEST_COVERAGE_MISMATCH")
|
||||
for name, path in files:
|
||||
if hashlib.sha256(path.read_bytes()).hexdigest() != observed[name]:
|
||||
raise PackageClosureError("MANIFEST_HASH_MISMATCH")
|
||||
return {
|
||||
"status": "PASS",
|
||||
"files_verified": len(files),
|
||||
"manifest_sha256": hashlib.sha256(manifest_path.read_bytes()).hexdigest(),
|
||||
}
|
||||
|
||||
|
||||
def _archive_mode(name: str) -> int:
|
||||
executable_prefixes = ("deploy/bin/", "deploy/actions/")
|
||||
leaf = PurePosixPath(name).name
|
||||
executable = name.startswith(executable_prefixes) and "." not in leaf
|
||||
return 0o100755 if executable else 0o100644
|
||||
|
||||
|
||||
def build_deterministic_zip(root: Path, output: Path, sidecar: Path) -> str:
|
||||
root = Path(root).resolve(strict=True)
|
||||
output, sidecar = Path(output).resolve(), Path(sidecar).resolve()
|
||||
if output == sidecar or output.is_relative_to(root) or sidecar.is_relative_to(root):
|
||||
raise PackageClosureError("ARCHIVE_OUTPUT_LOCATION_INVALID")
|
||||
validate_package(root)
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
sidecar.parent.mkdir(parents=True, exist_ok=True)
|
||||
with zipfile.ZipFile(
|
||||
output,
|
||||
mode="w",
|
||||
compression=zipfile.ZIP_DEFLATED,
|
||||
compresslevel=9,
|
||||
strict_timestamps=True,
|
||||
) as archive:
|
||||
for name, path in _fixed_files(root, include_manifest=True):
|
||||
info = zipfile.ZipInfo(name, date_time=(1980, 1, 1, 0, 0, 0))
|
||||
info.create_system = 3
|
||||
info.compress_type = zipfile.ZIP_DEFLATED
|
||||
info.external_attr = _archive_mode(name) << 16
|
||||
archive.writestr(info, path.read_bytes())
|
||||
digest = hashlib.sha256(output.read_bytes()).hexdigest()
|
||||
sidecar.write_text(f"{digest} {output.name}\n", encoding="ascii", newline="\n")
|
||||
return digest
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(prog="build-v5r1-package")
|
||||
parser.add_argument("--source", required=True, type=Path)
|
||||
parser.add_argument("--output-zip", required=True, type=Path)
|
||||
parser.add_argument("--sidecar", required=True, type=Path)
|
||||
parser.add_argument("--rewrite-manifest", action="store_true")
|
||||
args = parser.parse_args(argv)
|
||||
if args.rewrite_manifest:
|
||||
write_manifest(args.source)
|
||||
digest = build_deterministic_zip(args.source, args.output_zip, args.sidecar)
|
||||
print(json.dumps({"status": "PASS", "package_zip_sha256": digest}, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
# AIOS V2R1C4 End-to-End Execution Requirements V5
|
||||
|
||||
## Goal
|
||||
|
||||
Use only the V5 task-scoped control plane. Continue autonomously until
|
||||
V2R1C4 is fully deployed, freshly accepted, tagged, registered and set as
|
||||
the Production Baseline.
|
||||
|
||||
Do not report `COMPLETED` until every condition in
|
||||
`FINAL_ACCEPTANCE_CONTRACT_V5.md` is independently true.
|
||||
|
||||
## 1. Verify V5 delivery
|
||||
|
||||
Verify:
|
||||
|
||||
```text
|
||||
external ZIP SHA-256
|
||||
MANIFEST.sha256
|
||||
Wheel SHA-256
|
||||
SYSTEMD_UNITS.sha256
|
||||
Manifest Schema SHA-256
|
||||
runtime-layout Schema SHA-256
|
||||
```
|
||||
|
||||
Run the complete tests with zero failure and zero skip.
|
||||
|
||||
Reject V1, V2 or V3 units, paths, credentials, Wheels or deployment
|
||||
definitions.
|
||||
|
||||
## 2. Verify all six control-plane services
|
||||
|
||||
The service set is Anchor, Approval Observer, Writer, Controller, Publisher,
|
||||
and the root Action Broker. Every non-root role unit must use:
|
||||
|
||||
```text
|
||||
RuntimeDirectory=aios-v2r1c4-release-v5/<role>
|
||||
RuntimeDirectoryPreserve=no
|
||||
StateDirectory=aios-v2r1c4-release-v5/<role>
|
||||
```
|
||||
|
||||
The root Action Broker uses the separately created root-managed shared runtime
|
||||
parent. All sockets, state, environment and executable paths must be
|
||||
V5-scoped.
|
||||
|
||||
Do not create a drop-in to repair a mismatch. A drop-in is a new unapproved
|
||||
deployment definition.
|
||||
|
||||
## 3. Close the fixed Artifact's external dependency
|
||||
|
||||
Safely extract the fixed Release Artifact without following links.
|
||||
|
||||
Enumerate every symlink and special file. The only approved absolute link is:
|
||||
|
||||
```text
|
||||
vendor/openclaw-weixin/node_modules/openclaw
|
||||
→ /usr/lib/node_modules/openclaw
|
||||
```
|
||||
|
||||
Compute the full values:
|
||||
|
||||
```bash
|
||||
sha256sum /usr/lib/node_modules/openclaw/package.json
|
||||
|
||||
aios-task-release tree-sha256 \
|
||||
--root /usr/lib/node_modules/openclaw
|
||||
```
|
||||
|
||||
Verify:
|
||||
|
||||
```text
|
||||
package name=openclaw
|
||||
package version=2026.6.11
|
||||
package_json_sha256=<full 64-character value>
|
||||
target_tree_sha256=<full 64-character value>
|
||||
```
|
||||
|
||||
A digest prefix such as `fe854d…` is not acceptable.
|
||||
|
||||
Generate the exact candidate production unit. It must include:
|
||||
|
||||
```text
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ReadOnlyPaths=/usr/lib/node_modules/openclaw
|
||||
```
|
||||
|
||||
`BindReadOnlyPaths` is also acceptable. Compute and bind the exact unit
|
||||
SHA-256.
|
||||
|
||||
Generate and verify `runtime-layout.json`. Stop if any undeclared absolute
|
||||
link, escaping relative link, special file, package mismatch, Tree mismatch,
|
||||
unit mismatch or missing read-only binding exists.
|
||||
|
||||
## 4. Refreeze downstream release objects
|
||||
|
||||
Reuse only:
|
||||
|
||||
```text
|
||||
source_commit_oid=1857b88690cd1c0119df984534c260b6f217a21a
|
||||
source_tree_oid=17c153de2338f570f70cb7407e7dda3c596b524b
|
||||
release_artifact_sha256=39523caa48f39b33e5e5df785bb568e0381d8b20301746c280442f73f294f2b2
|
||||
rollback_artifact_sha256=dd0dbbe21db14722996636347ea1d22917436c688d5f0b7055b5bfb1776c4d95
|
||||
```
|
||||
|
||||
Put the approved runtime-layout digest into the Release Root input. Generate
|
||||
and verify the Release Root and Evidence Manifest.
|
||||
|
||||
Create one new evidence closure commit directly on the fixed source. Only
|
||||
approved evidence and generator inputs may be added. Product code must remain
|
||||
byte-identical.
|
||||
|
||||
Build a connected Git Bundle, verify ancestry and `git fsck`, then calculate
|
||||
the deterministic annotated V2R1C4 Tag object.
|
||||
|
||||
## 5. Create the exact Owner approval payload
|
||||
|
||||
Populate the final V5 Manifest with all exact OIDs, Trees and SHA-256 values.
|
||||
|
||||
Generate:
|
||||
|
||||
```bash
|
||||
aios-task-release approval-payload \
|
||||
--manifest <preapproval-manifest>
|
||||
```
|
||||
|
||||
Obtain one unexpired machine-readable Owner approval for the exact payload.
|
||||
|
||||
Do not reuse PR #34, a Phase-A review, a V3 approval or chat text.
|
||||
|
||||
## 6. Reconcile Authority main
|
||||
|
||||
Through the separately governed mirror mechanism, perform only the approved
|
||||
fast-forward:
|
||||
|
||||
```text
|
||||
875d41c3101093149414292067fd700dc214d4d3
|
||||
→
|
||||
8291b8368bf690416a3159ac5d21d9f8139c2e61
|
||||
```
|
||||
|
||||
Authority ref, Gitea ref and Gitea API must all equal the new OID. The
|
||||
task-scoped Writer must not receive general `main` permission.
|
||||
|
||||
## 7. Install only V5 services
|
||||
|
||||
Install:
|
||||
|
||||
```text
|
||||
aios-v2r1c4-release-anchor-v5.service
|
||||
aios-v2r1c4-release-writer-v5.service
|
||||
aios-v2r1c4-release-controller-v5.service
|
||||
aios-v2r1c4-release-publisher-v5.service
|
||||
```
|
||||
|
||||
Use distinct non-root identities and expiring exact ACLs.
|
||||
|
||||
- Writer: approved candidate branch only.
|
||||
- Controller: no Git/Gitea write credential.
|
||||
- Publisher: locked until all machine gates pass.
|
||||
|
||||
Keep:
|
||||
|
||||
```text
|
||||
AIOS_GITOPS_WRITES_ENABLED=false
|
||||
```
|
||||
|
||||
## 8. Execute the state machine
|
||||
|
||||
```text
|
||||
record_main_reconciliation
|
||||
import_exact_objects
|
||||
publish_exact_branch
|
||||
deploy_candidate
|
||||
migrate_owner_identity
|
||||
promote_production
|
||||
scan_core_metadata
|
||||
record_fresh_acceptance
|
||||
publish_exact_tag
|
||||
write_release_registry
|
||||
write_production_baseline
|
||||
revoke_task_credentials
|
||||
complete
|
||||
```
|
||||
|
||||
`deploy_candidate` is isolation-only and must prove production is unchanged;
|
||||
it must not emit a production-promotion receipt. `promote_production` is the
|
||||
only operation allowed to emit that receipt. Registry and baseline each use
|
||||
one Anchor reservation for the complete C3 then C4 read -> single governed
|
||||
commit-file -> wait -> reread sequence; C3 facts may not reuse C4 hashes.
|
||||
|
||||
Every operation-sequence entry has one content-addressed
|
||||
`approved_action_plans` object without a self-digest. All 13 transitions bind
|
||||
its canonical digest through the Manifest, approval payload, operation request
|
||||
`action_plan_sha256`, and the corresponding Anchor request. Reserving actions
|
||||
include it in `request_sha256`, fresh acceptance includes it in the atomic
|
||||
consumption request SHA, and `complete` sends it directly. The eight
|
||||
root-brokered transitions additionally bind
|
||||
`config.actions[action].plan_sha256` and the broker payload. Direct and
|
||||
Anchor-only transitions do not invent broker entries. API reads use only
|
||||
`127.0.0.1`; pushes use only `g.cnnetmkt.top`.
|
||||
|
||||
Before object import, the cache gate must independently verify:
|
||||
|
||||
```text
|
||||
Artifact and Rollback
|
||||
runtime layout
|
||||
candidate production unit
|
||||
Release Root
|
||||
Evidence Manifest
|
||||
Git Bundle
|
||||
```
|
||||
|
||||
## 9. Production acceptance
|
||||
|
||||
Verify rollback before the first production side effect.
|
||||
|
||||
Complete Owner identity migration with zero raw Owner ID occurrences and
|
||||
complete the core metadata scan.
|
||||
|
||||
Wait for a new natural trusted Owner WeChat protected-dispatch event. Do not
|
||||
inject, forge, replay or directly call the internal endpoint.
|
||||
|
||||
## 10. Formal publication
|
||||
|
||||
Only after every machine gate passes:
|
||||
|
||||
1. publish the exact annotated V2R1C4 Tag;
|
||||
2. verify Tag object and peeled target in Authority and Gitea;
|
||||
3. write Registry V2R1C4;
|
||||
4. update Production Baseline to V2R1C4 / `PRODUCTION_DEPLOYED`;
|
||||
5. revoke all temporary accounts, Tokens, ACLs and credential projections;
|
||||
6. complete the Anchor.
|
||||
|
||||
## 11. Automatic recovery
|
||||
|
||||
Automatically retry bounded transient network, TLS, DNS, startup,
|
||||
credential-projection and remote-visibility failures.
|
||||
|
||||
For an open Anchor reservation, reconcile the real effect before retrying.
|
||||
Automatically roll back a failed candidate deployment when the approved
|
||||
rollback asset remains verifiable.
|
||||
|
||||
Stop only for:
|
||||
|
||||
```text
|
||||
missing or expired approval
|
||||
non-fast-forward main
|
||||
fixed source or Artifact mismatch
|
||||
runtime-layout object mismatch
|
||||
same-name different-OID branch or Tag
|
||||
path outside approval
|
||||
real Secret detection
|
||||
unverified rollback
|
||||
fresh acceptance safety failure
|
||||
required change to an approved object
|
||||
```
|
||||
|
||||
## 12. Final report
|
||||
|
||||
Always include:
|
||||
|
||||
```text
|
||||
force_push_used=false
|
||||
tag_rewritten=false
|
||||
direct_main_push=false
|
||||
secrets_committed=false
|
||||
owner_approval_respected=true
|
||||
```
|
||||
|
||||
## Fixed V5R1 component digests
|
||||
|
||||
```text
|
||||
wheel_sha256=91c7452965eb668b8f696d83ce43950bfe61805aed29a161610c9ad12d0d7b19
|
||||
deployment_definitions_sha256=b1e7379b1d8cbc5c83b41fee278b2a85e115a6bd071f7e0670563659429b70c0
|
||||
manifest_schema_sha256=31bce5c2960d8c57b26e0fa854196c17e6f018cd547b58679b7877cb602df4f4
|
||||
runtime_layout_schema_sha256=658813b8a0096ab5ee66dd217dc0bbb1b08e09dc33241049f24190bb4df86c9d
|
||||
exact_two_parent_broker_sha256=eb98a9ef64bbbc26e0c0ea9e6f1b7ef247ae685b96ed2b8c0edba7acc39fa4e4
|
||||
broker_schema_sha256=72efc595a85b471260110197ac845171c8447ab4edbf1ded38aed5bee6fe6aba
|
||||
validator_schema_sha256=95c23c5e54b0307fd1ab67dad92edaebdee6d4c495d8009c19015775f75d1f22
|
||||
site_config_sha256=be44861e717513e67b532fdb2f20b67a96aad07e212d521d4a69148ca346760c
|
||||
```
|
||||
|
||||
The external ZIP SHA-256 is supplied beside the ZIP. The real production
|
||||
site-config, external OpenClaw Tree, full package.json digest and candidate
|
||||
production-unit digest must be calculated before generating the final Owner
|
||||
approval payload.
|
||||
+358
@@ -0,0 +1,358 @@
|
||||
# AIOS V2R1C4 End-to-End Execution Requirements V5
|
||||
|
||||
## Goal
|
||||
|
||||
Use only the V5 task-scoped control plane. Continue autonomously until
|
||||
V2R1C4 is fully deployed, freshly accepted, tagged, registered and set as
|
||||
the Production Baseline.
|
||||
|
||||
Do not report `COMPLETED` until every condition in
|
||||
`FINAL_ACCEPTANCE_CONTRACT_V5.md` is independently true.
|
||||
|
||||
## 1. Verify V5 delivery
|
||||
|
||||
Verify:
|
||||
|
||||
```text
|
||||
external ZIP SHA-256
|
||||
MANIFEST.sha256
|
||||
Wheel SHA-256
|
||||
SYSTEMD_UNITS.sha256
|
||||
Manifest Schema SHA-256
|
||||
runtime-layout Schema SHA-256
|
||||
```
|
||||
|
||||
Run the complete tests with zero failure and zero skip.
|
||||
|
||||
Reject V1, V2 or V3 units, paths, credentials, Wheels or deployment
|
||||
definitions.
|
||||
|
||||
## 2. Verify the task-role units and identities
|
||||
|
||||
Every unit must use:
|
||||
|
||||
```text
|
||||
RuntimeDirectory=aios-v2r1c4-release-v5
|
||||
RuntimeDirectoryPreserve=no
|
||||
StateDirectory=aios-v2r1c4-release-v5/<role>
|
||||
```
|
||||
|
||||
All sockets, state, environment and executable paths must be V5-scoped.
|
||||
|
||||
Do not create a drop-in to repair a mismatch. A drop-in is a new unapproved
|
||||
deployment definition.
|
||||
|
||||
Verify the installed unit identity and peer boundary exactly:
|
||||
|
||||
```text
|
||||
Anchor: aios-v2r1c4-task-anchor / aios-v2r1c4-release
|
||||
Approval observer: aios-v2r1c4-approval-observer / aios-v2r1c4-release
|
||||
Writer: aios-v2r1c4-task-writer / aios-v2r1c4-release
|
||||
Controller: aios-v2r1c4-task-controller / aios-v2r1c4-release
|
||||
Publisher: aios-v2r1c4-task-publisher / aios-v2r1c4-release
|
||||
Root action broker: root / root
|
||||
```
|
||||
|
||||
These are distinct service identities, not interchangeable labels. The Writer
|
||||
process also hosts the loopback metadata Writer runtime on `127.0.0.1:19892`;
|
||||
its metadata API bearer and Gitea publication credentials belong only to the
|
||||
Writer unit. The Publisher does not host or inherit those credentials.
|
||||
|
||||
## 3. Close the fixed Artifact's external dependency
|
||||
|
||||
Safely extract the fixed Release Artifact without following links.
|
||||
|
||||
Enumerate every symlink and special file. The only approved absolute link is:
|
||||
|
||||
```text
|
||||
vendor/openclaw-weixin/node_modules/openclaw
|
||||
→ /usr/lib/node_modules/openclaw
|
||||
```
|
||||
|
||||
Compute the full values:
|
||||
|
||||
```bash
|
||||
sha256sum /usr/lib/node_modules/openclaw/package.json
|
||||
|
||||
aios-task-release tree-sha256 \
|
||||
--root /usr/lib/node_modules/openclaw
|
||||
```
|
||||
|
||||
Verify:
|
||||
|
||||
```text
|
||||
package name=openclaw
|
||||
package version=2026.6.11
|
||||
package_json_sha256=<full 64-character value>
|
||||
target_tree_sha256=<full 64-character value>
|
||||
```
|
||||
|
||||
A digest prefix such as `fe854d…` is not acceptable.
|
||||
|
||||
Generate the exact candidate production unit. It must include:
|
||||
|
||||
```text
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ReadOnlyPaths=/usr/lib/node_modules/openclaw
|
||||
```
|
||||
|
||||
`BindReadOnlyPaths` is also acceptable. Compute and bind the exact unit
|
||||
SHA-256.
|
||||
|
||||
Generate and verify `runtime-layout.json`. Stop if any undeclared absolute
|
||||
link, escaping relative link, special file, package mismatch, Tree mismatch,
|
||||
unit mismatch or missing read-only binding exists.
|
||||
|
||||
## 4. Refreeze downstream release objects
|
||||
|
||||
Reuse only:
|
||||
|
||||
```text
|
||||
source_commit_oid=1857b88690cd1c0119df984534c260b6f217a21a
|
||||
source_tree_oid=17c153de2338f570f70cb7407e7dda3c596b524b
|
||||
release_artifact_sha256=39523caa48f39b33e5e5df785bb568e0381d8b20301746c280442f73f294f2b2
|
||||
rollback_artifact_sha256=dd0dbbe21db14722996636347ea1d22917436c688d5f0b7055b5bfb1776c4d95
|
||||
```
|
||||
|
||||
Put the approved runtime-layout digest into the Release Root input. Generate
|
||||
and verify the Release Root and Evidence Manifest.
|
||||
|
||||
Create one new evidence closure commit directly on the fixed source. Only
|
||||
approved evidence and generator inputs may be added. Product code must remain
|
||||
byte-identical.
|
||||
|
||||
Build a connected Git Bundle, verify ancestry and `git fsck`, then calculate
|
||||
the deterministic annotated V2R1C4 Tag object.
|
||||
|
||||
## 5. Create the exact Owner approval payload
|
||||
|
||||
Populate the final V5 Manifest with all exact OIDs, Trees and SHA-256 values.
|
||||
|
||||
Generate:
|
||||
|
||||
```bash
|
||||
aios-task-release approval-payload \
|
||||
--manifest <preapproval-manifest>
|
||||
```
|
||||
|
||||
Obtain one unexpired machine-readable Owner approval for the exact payload.
|
||||
|
||||
Do not reuse PR #34, a Phase-A review, a V3 approval or chat text.
|
||||
|
||||
## 6. Reconcile Authority main
|
||||
|
||||
Through the separately governed mirror mechanism, perform only the approved
|
||||
fast-forward:
|
||||
|
||||
```text
|
||||
875d41c3101093149414292067fd700dc214d4d3
|
||||
→
|
||||
8291b8368bf690416a3159ac5d21d9f8139c2e61
|
||||
```
|
||||
|
||||
Authority ref, Gitea ref and Gitea API must all equal the new OID. The
|
||||
task-scoped Writer must not receive general `main` permission.
|
||||
|
||||
## 7. Install only V5 services
|
||||
|
||||
Install:
|
||||
|
||||
```text
|
||||
aios-v2r1c4-release-anchor-v5.service
|
||||
aios-v2r1c4-release-approval-observer-v5.service
|
||||
aios-v2r1c4-release-writer-v5.service
|
||||
aios-v2r1c4-release-controller-v5.service
|
||||
aios-v2r1c4-release-publisher-v5.service
|
||||
aios-v2r1c4-root-action-broker.service
|
||||
```
|
||||
|
||||
Use the exact identities listed in section 2 and expiring exact ACLs. Only the
|
||||
root action broker is root; all other roles are distinct non-root identities.
|
||||
|
||||
- Writer: approved candidate branch only.
|
||||
- Controller: no Git/Gitea write credential.
|
||||
- Publisher: locked until all machine gates pass.
|
||||
|
||||
Keep:
|
||||
|
||||
```text
|
||||
AIOS_GITOPS_WRITES_ENABLED=false
|
||||
```
|
||||
|
||||
## 8. Execute the state machine
|
||||
|
||||
```text
|
||||
record_main_reconciliation
|
||||
import_exact_objects
|
||||
publish_exact_branch
|
||||
deploy_candidate
|
||||
migrate_owner_identity
|
||||
promote_production
|
||||
scan_core_metadata
|
||||
record_fresh_acceptance
|
||||
publish_exact_tag
|
||||
write_release_registry
|
||||
write_production_baseline
|
||||
revoke_task_credentials
|
||||
complete
|
||||
```
|
||||
|
||||
`deploy_candidate` is isolation-only and must prove production is unchanged;
|
||||
it must not emit a production-promotion receipt. `promote_production` is the
|
||||
only operation allowed to emit that receipt, and post-promotion scan and fresh
|
||||
acceptance cannot run before it commits. Registry and baseline each execute
|
||||
their entire C3 then C4 transition under one Anchor reservation, using
|
||||
read -> one governed `/api/commit-file` write -> visibility wait -> reread for
|
||||
each stage. Historical C3 Artifact hashes must be independently supplied and
|
||||
must not equal the fixed C4 hashes.
|
||||
|
||||
Before every state transition, compute the canonical SHA-256 of the exact
|
||||
`approved_action_plans[action]` object. All 13 transitions require equality
|
||||
between the Manifest plan, approval payload, and operation request
|
||||
`action_plan_sha256`. Reserving transitions include the digest in the Anchor
|
||||
request SHA; fresh acceptance includes it in the atomic consumption request
|
||||
SHA; `complete` submits it directly to Anchor. The eight root-brokered
|
||||
transitions additionally require
|
||||
equality with `config.actions[action].plan_sha256` and the broker request
|
||||
payload. The five direct or Anchor-only transitions do not invent broker
|
||||
configuration entries. Plans cover the complete `operation_sequence`,
|
||||
including `complete`, and never contain a `plan_sha256` field themselves. API
|
||||
observation is restricted to `127.0.0.1`; Git push is restricted to
|
||||
`g.cnnetmkt.top`.
|
||||
|
||||
For the eight brokered transitions, `_run_bound` must emit the exact wrapper
|
||||
shape with `execute=true`, authorization, idempotency key, Manifest digest and
|
||||
request plan digest. The broker rejects the legacy three-field request,
|
||||
authenticates the peer role, and injects its startup-pinned action-program and
|
||||
canonical config SHA-256 into the strict action-program payload. Anchor reserve
|
||||
remains solely in the operation service; broker execution must not reserve or
|
||||
advance Anchor independently.
|
||||
|
||||
Before executing, the root broker must resolve the Anchor snapshot as the
|
||||
configured caller identity and prove all three of the following: the Manifest
|
||||
digest matches, the campaign state equals the action's required Anchor state,
|
||||
and exactly one open reservation matches the action and idempotency key. A
|
||||
missing reservation is `ANCHOR_RESERVATION_MISSING`; state drift is fail-closed.
|
||||
For actions with predecessors, the broker accepts only committed Anchor PASS
|
||||
envelopes whose producer plan digest matches the broker configuration. It
|
||||
writes their canonical JSON to a root-created, mode-0400 memfd sealed against
|
||||
write, grow, shrink and further seal changes, then passes only that descriptor
|
||||
to the privilege-dropped action child. Caller JSON containing
|
||||
`anchored_predecessor_evidence` is rejected.
|
||||
|
||||
Every Anchor terminal envelope, including failures, must contain exactly:
|
||||
|
||||
```text
|
||||
authorization_id
|
||||
manifest_sha256
|
||||
operation
|
||||
producer_action_plan_sha256
|
||||
idempotency_key
|
||||
status # PASS or FAIL
|
||||
payload
|
||||
evidence_sha256 # SHA-256 of the canonical seven fields above
|
||||
chain_hash
|
||||
```
|
||||
|
||||
The snapshot must place PASS envelopes in `committed_operations` and FAIL
|
||||
envelopes in `failed_operations`; status/state disagreement, missing envelope,
|
||||
or digest mismatch is corruption and stops execution.
|
||||
|
||||
Do not conflate quarantine scopes. `record_main_reconciliation` is pinned to
|
||||
`/run/aios-v2r1c4-release-v5/quarantine/main-reconciliation.git`; writer-side
|
||||
object import remains scoped by site config to
|
||||
`/var/lib/aios-v2r1c4-release-v5/quarantine`.
|
||||
|
||||
Before object import, the cache gate must independently verify:
|
||||
|
||||
```text
|
||||
Artifact and Rollback
|
||||
runtime layout
|
||||
candidate production unit
|
||||
Release Root
|
||||
Evidence Manifest
|
||||
Git Bundle
|
||||
```
|
||||
|
||||
## 9. Production acceptance
|
||||
|
||||
Verify rollback before the first production side effect.
|
||||
|
||||
This is a live check, not a Manifest assertion: observe current production
|
||||
active/health state, verify that the rollback archive is present, readable and
|
||||
matches its approved SHA-256, and verify the rollback worktree's exact commit
|
||||
and tree OIDs. `promote_production` must emit that observation and
|
||||
`rollback_verified_before_switch=true`. If promotion fails, verify the restored
|
||||
C3 service and health; an attempted rollback without verified restoration is a
|
||||
terminal block.
|
||||
|
||||
Complete Owner identity migration with zero raw Owner ID occurrences and
|
||||
complete the core metadata scan.
|
||||
|
||||
Wait for a new natural trusted Owner WeChat protected-dispatch event. Do not
|
||||
inject, forge, replay or directly call the internal endpoint.
|
||||
|
||||
Approval binds the content-addressed fresh-acceptance derivation contract, its
|
||||
templates, owner principal, release target, runtime layout, production unit,
|
||||
observer status, and dynamic JSON pointer
|
||||
`/fresh_acceptance_evidence_sha256`. It does not bind a guessed future evidence
|
||||
file or evidence digest. The concrete digest is produced only by the new event,
|
||||
revalidated by the independent observer as
|
||||
`SIGNATURE_RECOMPUTED_AND_REPLAY_CLEAR`, and atomically committed by Anchor.
|
||||
|
||||
## 10. Formal publication
|
||||
|
||||
Only after every machine gate passes:
|
||||
|
||||
1. publish the exact annotated V2R1C4 Tag;
|
||||
2. verify Tag object and peeled target in Authority and Gitea;
|
||||
3. write Registry V2R1C4;
|
||||
4. update Production Baseline to V2R1C4 / `PRODUCTION_DEPLOYED`;
|
||||
5. revoke all temporary accounts, Tokens, ACLs and credential projections;
|
||||
6. complete the Anchor.
|
||||
|
||||
## 11. Automatic recovery
|
||||
|
||||
Automatically retry bounded transient network, TLS, DNS, startup,
|
||||
credential-projection and remote-visibility failures.
|
||||
|
||||
For an open Anchor reservation, reconcile the real effect before retrying.
|
||||
Automatically roll back a failed candidate deployment when the approved
|
||||
rollback asset remains verifiable.
|
||||
|
||||
Stop only for:
|
||||
|
||||
```text
|
||||
missing or expired approval
|
||||
non-fast-forward main
|
||||
fixed source or Artifact mismatch
|
||||
runtime-layout object mismatch
|
||||
same-name different-OID branch or Tag
|
||||
path outside approval
|
||||
real Secret detection
|
||||
unverified rollback
|
||||
fresh acceptance safety failure
|
||||
required change to an approved object
|
||||
```
|
||||
|
||||
## 12. Final report
|
||||
|
||||
Always include:
|
||||
|
||||
```text
|
||||
force_push_used=false
|
||||
tag_rewritten=false
|
||||
direct_main_push=false
|
||||
secrets_committed=false
|
||||
owner_approval_respected=true
|
||||
```
|
||||
|
||||
## Closure-generated V5 component evidence
|
||||
|
||||
The external ZIP SHA-256 is supplied beside the ZIP. Wheel, deployment
|
||||
definition, schema, Manifest, systemd and package digests and the final test
|
||||
totals are generated and recorded by the closure rebuild; this document does
|
||||
not predict them. The real production site-config, external OpenClaw Tree,
|
||||
full package.json digest and candidate production-unit digest must be
|
||||
calculated before generating the final Owner approval payload.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Code Development Report V5
|
||||
|
||||
V5 succeeds V4. Fixed product source and input-artifact hashes are unchanged;
|
||||
the following V5 implementation work changes only the release-control-plane
|
||||
package and must be reflected by rebuilt package digests.
|
||||
|
||||
Implemented:
|
||||
|
||||
- six V5-isolated systemd service definitions and V5 tmpfiles coverage;
|
||||
- regression tests for exact runtime/state paths and effective unit/drop-in
|
||||
evidence;
|
||||
- task Manifest schema v5 plus `AIOS_RUNTIME_LAYOUT_V1` Release Root
|
||||
cross-binding;
|
||||
- safe tar extraction that rejects traversal, special files, undeclared
|
||||
absolute links, and escaping link targets;
|
||||
- binding of the actual `--release-artifact` archive to the generated extracted
|
||||
Artifact file manifest;
|
||||
- writable-path overlap rejection for the fixed read-only OpenClaw dependency;
|
||||
- external dependency package metadata/tree validation and symlink policy;
|
||||
- mandatory candidate/effective production-unit digest equality and explicit
|
||||
no-drop-in verification, including `ProtectSystem=strict`,
|
||||
`NoNewPrivileges=true`, systemd list-reset handling, and read-only bindings;
|
||||
- V5-scoped systemd paths in the manifest and validation contract;
|
||||
- CLI support for `--release-artifact`, mandatory effective-unit/no-drop-in
|
||||
inputs, `artifact-extract`, and `artifact-file-manifest-generate`;
|
||||
- exact fixed OpenClaw dependency identity, safe archive snapshot parsing,
|
||||
bounded member counts, and complete special-member pre-scan;
|
||||
- strict publisher-unlock receipt/evidence/OID binding and path-safe quarantine
|
||||
validation;
|
||||
- cache-gate integration and tamper coverage;
|
||||
- durable full Anchor PASS/FAIL evidence envelopes with digest and chain
|
||||
validation;
|
||||
- root-broker enforcement of an existing Anchor reservation and exact campaign
|
||||
state, with Anchor-resolved predecessors delivered through a sealed memfd;
|
||||
- caller-predecessor rejection and thin AF_UNIX broker passthrough wrappers;
|
||||
- Writer-role hosting of the loopback metadata Writer and its isolated
|
||||
credentials;
|
||||
- live pre-switch rollback artifact/worktree/production-health verification and
|
||||
verified restoration reporting;
|
||||
- a content-addressed fresh-acceptance derivation contract that binds templates
|
||||
and the dynamic JSON pointer without predicting a future evidence digest.
|
||||
|
||||
Validation totals and package/component hashes are generated by the final
|
||||
closure run and recorded in its machine-readable reports. This development
|
||||
report does not predict those values. Package rebuild, digest generation,
|
||||
human Phase B approval, live deployment, and fresh production acceptance remain
|
||||
separate release gates.
|
||||
|
||||
No AIOS/Gitea ref, system service, account, token, ACL, production Artifact,
|
||||
tag, registry, or baseline was changed during development.
|
||||
|
||||
## Digest status
|
||||
|
||||
Stale V4 component digest claims have been removed. Every V5 package/component
|
||||
digest is closure-generated and is recorded only after the final rebuild. Fixed
|
||||
product source and input-artifact hashes remain unchanged and are not replaced
|
||||
by future V5 package digests.
|
||||
|
||||
No production action is authorized by this report. The human Phase B gate is
|
||||
mandatory, and no production effect may occur before exact approval is present,
|
||||
current and independently observed.
|
||||
@@ -0,0 +1,86 @@
|
||||
# Control-Plane Fixes V5
|
||||
|
||||
V5 succeeds V4 and preserves the fixed product source and input-artifact
|
||||
hashes. It replaces V4 closure assumptions with a schema-v5, evidence-backed
|
||||
control-plane contract.
|
||||
|
||||
## V5-scoped systemd contract
|
||||
|
||||
All V5 services use only the V5 namespace:
|
||||
|
||||
```text
|
||||
/run/aios-v2r1c4-release-v5
|
||||
/var/lib/aios-v2r1c4-release-v5/<role>
|
||||
/etc/aios/v2r1c4-release-v5
|
||||
/opt/aios/v2r1c4-release-v5
|
||||
```
|
||||
|
||||
Validation rejects V3 or unversioned runtime/state paths, non-V5 tmpfiles,
|
||||
and V5 unit definitions whose effective configuration escapes these paths.
|
||||
|
||||
The evidence subject is the approved production fragment plus an independently
|
||||
captured effective fragment. Their digests must match and the resolved drop-in
|
||||
set must be explicitly empty. Deployment evidence separately binds the
|
||||
installed fragment path, digest and empty `DropInPaths`, together with
|
||||
`ProtectSystem=strict`, `NoNewPrivileges=true`, and the required
|
||||
`ReadOnlyPaths` or `BindReadOnlyPaths`.
|
||||
|
||||
## Artifact and runtime-layout closure
|
||||
|
||||
Safe extraction rejects archive traversal entries, special files, excessive
|
||||
member counts, symlink ancestors and undeclared or escaping links. Tar parsing
|
||||
uses a private snapshot populated through the same descriptor that computes
|
||||
the expected archive digest. The closure binds that archive and a generated
|
||||
extracted Artifact file manifest. It rejects writable-path overlap with the
|
||||
mandatory read-only external dependency.
|
||||
|
||||
Task Manifest schema v5 records:
|
||||
|
||||
- the fixed Artifact digest and the extracted-file-manifest digest;
|
||||
- declared external symlinks and external dependency metadata/tree digest;
|
||||
- the candidate/effective production-unit digest and explicit no-drop-in
|
||||
evidence;
|
||||
- V5-scoped paths, read-only bindings, and writable-path non-overlap;
|
||||
- extraction, symlink, and special-file policies.
|
||||
|
||||
`artifact-extract` and `artifact-file-manifest-generate` provide the required
|
||||
safe-extraction and file-manifest inputs before `runtime-layout-generate` or
|
||||
`runtime-layout-verify` is run.
|
||||
|
||||
## Release Root binding
|
||||
|
||||
The Release Root binds the approved runtime-layout SHA-256. Any change to the
|
||||
archive, extracted file manifest, candidate/effective unit or drop-in set, external
|
||||
dependency, read-only binding, or permitted writable path changes the closure
|
||||
and requires regenerated approval evidence.
|
||||
|
||||
V4 component digests are not V5 evidence. V5 package/component digests remain
|
||||
closure-generated until final packaging; fixed product source and input-artifact
|
||||
hashes remain unchanged.
|
||||
|
||||
## Anchor-owned execution evidence
|
||||
|
||||
The operation service alone reserves and commits or fails an Anchor operation.
|
||||
The root broker must find the matching existing reservation and exact campaign
|
||||
state before it runs an action. It resolves predecessor results from Anchor,
|
||||
validates the full PASS envelopes and their producer plan bindings, and passes
|
||||
the canonical list through a root-created mode-0400 memfd sealed against write,
|
||||
grow and shrink. Neither operation-service JSON nor wrapper JSON is an
|
||||
authoritative predecessor channel.
|
||||
|
||||
PASS and FAIL are both durable full envelopes containing authorization,
|
||||
Manifest, operation, producer plan, idempotency key, status, payload, canonical
|
||||
evidence digest and chain hash. A failure is retained in `failed_operations`
|
||||
and releases its reservation quota without advancing state.
|
||||
|
||||
The deployed wrapper is a thin AF_UNIX broker passthrough. It validates strict
|
||||
request/response shapes and evidence digests, removes service-only predecessor
|
||||
data, and forwards no credential material. Production actions execute only in
|
||||
the pinned root-broker action program; `live_actions` remains non-production
|
||||
compatibility/test code.
|
||||
|
||||
The Writer service, under `aios-v2r1c4-task-writer`, hosts the loopback metadata
|
||||
Writer and alone receives its two credentials. Registry and baseline C4 bytes
|
||||
are derived after Anchor has durably recorded the natural fresh acceptance.
|
||||
Approval therefore binds the derivation contract and templates, never a future
|
||||
evidence filename or guessed evidence SHA-256.
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
# External Closure Requirements V5
|
||||
|
||||
Code can generate and verify the runtime layout, Release Root, Evidence
|
||||
Manifest, evidence closure, Git Bundle and Tag.
|
||||
|
||||
It cannot manufacture these external governance decisions:
|
||||
|
||||
1. authorization for the exact Authority main fast-forward;
|
||||
2. an unexpired machine-readable Owner approval for the final V5 payload;
|
||||
3. task-scoped Writer and Publisher credentials;
|
||||
4. exact Authority filesystem ACLs for those temporary identities.
|
||||
|
||||
The Owner approval must bind:
|
||||
|
||||
- V5 ZIP, Wheel and deployment-definition digests;
|
||||
- the real site-config digest;
|
||||
- Manifest and runtime-layout Schema digests;
|
||||
- source/evidence OIDs and trees;
|
||||
- Release Artifact and Rollback digests;
|
||||
- runtime-layout, Release Root and Evidence Manifest digests;
|
||||
- candidate production unit digest;
|
||||
- external OpenClaw package and Tree digests;
|
||||
- Git Bundle and deterministic Tag OID.
|
||||
|
||||
PR #34, an expired Phase-A review, a V3 approval or chat text is not
|
||||
sufficient.
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
# Final Acceptance Contract V5
|
||||
|
||||
Completion requires:
|
||||
|
||||
```text
|
||||
control_plane_systemd_isolation=PASS
|
||||
runtime_layout_closure=PASS
|
||||
absolute_artifact_symlink_count=1
|
||||
undeclared_absolute_symlink_count=0
|
||||
openclaw_package_json_sha256=<approved full SHA-256>
|
||||
openclaw_package_version=2026.6.11
|
||||
openclaw_target_tree_sha256=<approved SHA-256>
|
||||
production_unit_read_only_binding=PASS
|
||||
|
||||
release_root_generation=PASS
|
||||
evidence_manifest_generation=PASS
|
||||
|
||||
approved_tag_oid
|
||||
= local_tag_oid
|
||||
= authority_tag_oid
|
||||
= gitea_tag_ref_oid
|
||||
|
||||
approved_target_oid
|
||||
= local_peeled_target_oid
|
||||
= authority_peeled_target_oid
|
||||
= gitea_api_target_oid
|
||||
= production_target_oid
|
||||
= registry_target_oid
|
||||
= baseline_target_oid
|
||||
|
||||
authority_main_oid
|
||||
= gitea_main_ref_oid
|
||||
= gitea_main_api_oid
|
||||
= 8291b8368bf690416a3159ac5d21d9f8139c2e61
|
||||
|
||||
production_status=ACTIVE
|
||||
production_acceptance=PASS
|
||||
registry_version=V2R1C4
|
||||
production_baseline=V2R1C4
|
||||
baseline_status=PRODUCTION_DEPLOYED
|
||||
|
||||
temporary_accounts_remaining=0
|
||||
temporary_credentials_remaining=0
|
||||
old_tokens_http_status=401
|
||||
anchor_state=COMPLETE
|
||||
anchor_open_operations=[]
|
||||
AIOS_GITOPS_WRITES_ENABLED=false
|
||||
```
|
||||
@@ -0,0 +1,150 @@
|
||||
b016f98c7100546e703a3bda1416b3e2b309ae9ce37a520d3f9cf42f4d9894b7 AGENT_EXECUTION_REQUIREMENTS.md
|
||||
4c621b1ee915922808f05a0e39a03e837cb9a5038f37a72a101743e1d6ee26fd AGENT_EXECUTION_REQUIREMENTS_V5.md
|
||||
4cfffaa3efc10eec80a50e839c411f29e6bedf781d11fc837367d1120927ddf4 CODE_DEVELOPMENT_REPORT_V5.md
|
||||
3b66b3093d9c140ea733123a1cc3540307b033fe518a3af5e4d06abd47376c42 CONTROL_PLANE_FIXES_V5.md
|
||||
f2a5917057bdcb70ef2a1b0384e6738d9178a4571ba2bdd2371dddb78edd51a9 EXTERNAL_CLOSURE_REQUIREMENTS_V5.md
|
||||
85d4682832d475b03dcc99587a6b3d009d9a5f0ebf2036b43d35d168ccdcde87 FINAL_ACCEPTANCE_CONTRACT_V5.md
|
||||
008a40f85b6f2eb1b033a9df2e20cbdd2141d6371e6a86db0e2743c0da515699 OFFLINE_TEST_REPORT.json
|
||||
c45efc3d2de7aafa90a8d943c0f9886180cc01e548c00fb39c394a3252b71a20 PACKAGE_AUDIT.json
|
||||
02de7d0a1f7602a196dcb458730ccc84082f6695a9960d4b1758c8a0abe8e2d9 PACKAGE_CLOSURE_V5R1.md
|
||||
3f558c661cfa1d362deb1aa688f233b5ba2a2f75432a2462cb206da6e8e0ce79 PACKAGE_COMPONENTS.json
|
||||
7a7adae172684fe117282b61b60d2a86b2722bd12222c862bbc63cd5a912fdbb README.md
|
||||
c3a926e2e55d37998ddbaac1ca161d19300d42cdd4c9ef6cd7a24646ea107fce RECOVERY_AND_RECONCILIATION_V5.md
|
||||
45a181e41e8eb6de13098d9a8e9468bad5d366153daabcc1b2a4ac115daf6398 RELEASE_REFREEZE_WORKFLOW_V5.md
|
||||
df08f6023949f8dfc5b0b1d3143b1408db2eb96787eaa42d405f0aed27c4a4f7 RUNTIME_LAYOUT_CLOSURE_V5.md
|
||||
b1e7379b1d8cbc5c83b41fee278b2a85e115a6bd071f7e0670563659429b70c0 SYSTEMD_UNITS.sha256
|
||||
939495d29782d1f7c063131dcd134c31df0ad07079ab15df99c5886efb3705a6 deploy/actions/action-config.schema.json
|
||||
413313a03547372988ec871ff30d3fabe767d1c6af13f7f1392c71e7251d8196 deploy/actions/aios-v2r1c4-root-action-runner
|
||||
162d87f32aa21aed794f89a582a67532415980eed9161781bb6618c5cf88b178 deploy/actions/deploy-candidate
|
||||
70aaf4febf1c94674c64cef534b15ff0d314e0eb988c1bb7604038c8619a71fb deploy/actions/migrate-owner-identity
|
||||
232fa6e7737e2d505afe8ed066f6dac6098c40cd6c3092dc56c8374c8f771f1f deploy/actions/promote-production
|
||||
97b51f4daf26e07380b0f4f57d8cf8e449da1ec5bcbf1f134d52d53b3aa4c7f0 deploy/actions/revoke-task-credentials
|
||||
8d26b43a886477a130d249825a0dba21ad29d0708332caf2ce1e68605fa20352 deploy/actions/scan-core-metadata
|
||||
1baa7571b2ff22c60a2ca8b7174e7f22615c048d753793b28c0ebdd93da903ba deploy/actions/write-production-baseline
|
||||
a239b785a9ddd5975ae10d9820b6d690d161b37e67c291d908f3ee9646db4983 deploy/actions/write-release-registry
|
||||
c5d20a1c90a713b5d17f95638cfec55b84dbeb05122d5269485d8f70fbc598d5 deploy/bin/aios-task-release
|
||||
437f8826bf84ba8e3876bd12f2ccc274d1f498424c08b6a44b1e2e0963a1ece7 deploy/bin/aios-task-release-anchor
|
||||
5f4f83c850240acf8bea951408911dd032b201c8ba60cbc912eaf54ba7d52f5c deploy/bin/aios-task-release-finalizer
|
||||
5a5ec858035ce59b519b12018f881301d4ff59d281bab840413f4f88b0a4e435 deploy/bin/aios-task-release-operation
|
||||
dc99c98da6ed5cd4af5a5a2c5a64daf96eefeda39497fd3bd2beba2c76b5846c deploy/bin/aios-task-release-revoker
|
||||
ca76dd130f0969fcbb5418c4276d4fbbe0671b340ffb2c615098a9989064a8fd deploy/systemd/aios-exact-two-parent-merge-broker@.service
|
||||
8f564cf7658b1f27666216e57de133677251c9fec298c04444b4b2764a4c2d49 deploy/systemd/aios-v2r1c4-release-anchor-v5.service
|
||||
e9cf179a5cc75d1dfa644698abad7991acec582fd988ac73f01ba47b37595325 deploy/systemd/aios-v2r1c4-release-approval-observer-v5.service
|
||||
d1f42014f73a64aa4662d4071a0ccc5523131bbac2b72267548d548d12aba1d8 deploy/systemd/aios-v2r1c4-release-controller-v5.service
|
||||
fdee4f640bac52d4e85806ad9681c7ec5b7ab695326232308d156c65b5e078be deploy/systemd/aios-v2r1c4-release-publisher-v5.service
|
||||
138f811d5f4694cb9603718fadfcf6ce8041c348905812f0b0d48cfdae905f51 deploy/systemd/aios-v2r1c4-release-writer-v5.service
|
||||
5dc58ac4a4f724c276be9f914e76f5ff17ab0eed13086e1c594e2237bfc8c859 deploy/systemd/aios-v2r1c4-root-action-broker.service
|
||||
440ee7ccac9da3861c04cfaa22768a11c4905f2874d2d776adbdd499de29ef22 deploy/tmpfiles.d/aios-v2r1c4-task-release-v5.conf
|
||||
f06f8b2aaf0b8127bceb1ab34813a41d2187d1baf9691e6e133c82295122eb42 dist/action-broker.pyz
|
||||
087c8b66ed95fd5a464dec6b5ffe4b5aba922d3783f069c875ac3a50c1631020 dist/action-program.pyz
|
||||
91c7452965eb668b8f696d83ce43950bfe61805aed29a161610c9ad12d0d7b19 dist/aios_task_scoped_release-1.4.0-py3-none-any.whl
|
||||
f6df654f786916c277dac30da90c39fc3eadc137acb1ccfe54cd31cd89f57565 examples/01-anchor-register-request.example.json
|
||||
ed8aada31f951a9d41008d0c1b2d00c4fc19602cd8385d2de84469d58f898900 examples/02-main-reconciliation-request.example.json
|
||||
03161645c6d65a74076faeaee93ae8c4e27a5ad71096492b3979352e11e4de51 examples/03-import-objects-request.example.json
|
||||
f74951ab0e833a18a19442201d246bea0019b0a7e058d1de044759b6029a1e86 examples/04-publish-branch-request.example.json
|
||||
0b4c6a81f4bd662d1536672239af11a0fd0a5d307525c719a0b176ca75b2274b examples/05-deploy-candidate-request.example.json
|
||||
5cbab1907938a7fe95f8dc63324e0694983fcb04e73546181006dedd3f67de84 examples/06-migrate-owner-request.example.json
|
||||
5457ca42d3b3003f5b515c2534904086e7c78aeb063ac0d347818da121e6bd74 examples/07-core-scan-request.example.json
|
||||
91fbd141ca2c6ef228d10cbf6b785d8bf29b43e2958894356a7ab44156dcb199 examples/07-promote-production-request.example.json
|
||||
2852a013ee9ba2fdb6d22e1139eb8ad1212e9640f0d5e773bb4dbebd8acccd10 examples/08-fresh-acceptance-request.example.json
|
||||
094937b8503ef85acf9bb734c0057fb8cfb5ddab882ec0a3b9b43011d888ceca examples/09-publish-tag-request.example.json
|
||||
c10ba5c742278336be74c557c725c38f9cf278e834fb2c675a9d6e08a065782e examples/10-registry-request.example.json
|
||||
a9799ff1cc8f57f2ad196717dac6b3a136a3c529bd46ee56a2e2759221a21c9a examples/11-baseline-request.example.json
|
||||
0b64c01ad9b7412dd3099dc506cd69a67d7410f9081c36e2f0334614b45ce22e examples/12-revoke-request.example.json
|
||||
ae94b5f8ff593ec4222a0bbc014f9b2d42cdfa021caadd2915b7eb83261d64e0 examples/13-complete-request.example.json
|
||||
07f1e7df8dd4d65b20dbcf7c2cc5648c8c415634244b9a672d95905a175b0e21 examples/core-scan-evidence.example.json
|
||||
48e69c3ad054c751417ccde7f499e2ee63c852386d2f02098315cd8ed257e50d examples/deployment-evidence.example.json
|
||||
5a8f3ba118d3520d5fd1bff776eba9607d21f3827b2019ffd3177b4fc4091a70 examples/evidence-closure-input.example.json
|
||||
d8a680b5178921afed0423cd0da667735932d4db9ecf851ad2fd467762a4428e examples/evidence-manifest.example.json
|
||||
58c112e7cbd2826d2db36181b49d35f4ec1b4544832a3405cced5f0323d09d64 examples/fresh-acceptance.example.json
|
||||
093e892c1f8b004bb24061ed29488d86bad2531a5286e9b7ddc4675a93e25d3b examples/main-reconciliation-evidence.example.json
|
||||
7d68a774a974743784469b53c9d7507991c1d14d7dcf775125716e2e3399a0b4 examples/migration-evidence.example.json
|
||||
a93b7bef1cc7c6e2095c977b2ff96c376b18fd6addde26d5e3903bd370cc2b01 examples/production-promotion-evidence.example.json
|
||||
a85fd6da1700d725bbc52586349d4519120db59abd208a6369c654e7ec50cb86 examples/publisher-unlock.example.json
|
||||
d625ad2a43a281d2893b4097ce703ab2a6e9e56f279fce5f7a4dc52dbe641c7a examples/release-approval-payload.example.json
|
||||
226a25eaf8d84a924645b792e7928c90ed4ad03c5e178da73f77a5cc3c51068f examples/release-root-input.example.json
|
||||
f7d1285670928b1927b8b74ff5bc126ceb72c2b5858c1e259ecbdfb177d469a6 examples/release-root.manifest.example.json
|
||||
a733996ca18c2717572a20f99e3751687530ffb23dbda7e24adf606234a5d833 examples/runtime-layout/aios-v2r1c4-production.service
|
||||
a426ac32d35c1154e2b47870463142c1084991e2d4de4e72b99ff93327f1f0b8 examples/runtime-layout/runtime-layout.example.json
|
||||
be44861e717513e67b532fdb2f20b67a96aad07e212d521d4a69148ca346760c examples/site-config.example.json
|
||||
8966f8326fa13bd1d3a5b79387601502309a9c7ff6d1e482b271e260128d8bdb examples/task-scoped-release.example.json
|
||||
805d34a78d76e6e6c857c2465f5649369c0eaa7aa9a76d1a5f2856203d3ca9b2 examples/uids.env.example
|
||||
eb98a9ef64bbbc26e0c0ea9e6f1b7ef247ae685b96ed2b8c0edba7acc39fa4e4 governance/aios_single_owner_governance/exact_two_parent_merge_broker.py
|
||||
42896c609a6a0fddc3a2f408362d8c71121e212cb159130facc0a58de2a2a6b4 governance/aios_single_owner_governance/task_status_merge_broker.py
|
||||
95c23c5e54b0307fd1ab67dad92edaebdee6d4c495d8009c19015775f75d1f22 governance/exact-two-parent-merge-closure-v1.schema.json
|
||||
72efc595a85b471260110197ac845171c8447ab4edbf1ded38aed5bee6fe6aba governance/exact-two-parent-merge-grant-v1.schema.json
|
||||
864cdce2712af7a8cc7b50a5b4a46159addaae615b4f0879da9d450475b8b5fc pyproject.toml
|
||||
d2e3c6e6d93e633256218d88c457a85213b2039716f096fc49334d3cce0361d1 schemas/candidate-deployment-evidence-v1.schema.json
|
||||
69e70b5b77196f93f77e8af18a66395bdde91fac15947362e61f531e8fd686a0 schemas/core-metadata-scan-evidence-v1.schema.json
|
||||
90ca01c457927e87227dd4e3bbfa2d3678a68db6004a668c90dfee7ae924bbe8 schemas/evidence-closure-input-v1.schema.json
|
||||
faaba4a90a08e1e23243d4968955076d8ef1eab5d8108778976bd51822658218 schemas/evidence-closure-manifest-v1.schema.json
|
||||
205570100a0fcbd6919ac7c74e2942c0cc3720f2c804e3a4e4f05bf541815575 schemas/fresh-protected-dispatch-acceptance-v1.schema.json
|
||||
190f4b5bf366f0c67ae495f49ba2094eaca4b4d14bbf3e61f621f32b0b8c6e93 schemas/main-reconciliation-evidence-v1.schema.json
|
||||
9e63d97bf634913eae5796055df678ff9484a033e756379de628355be011bac8 schemas/owner-identity-migration-evidence-v1.schema.json
|
||||
2b37ed039e7fa20d4e796bc38593260572f38e9371910c88103c4d09c558259d schemas/production-promotion-evidence-v1.schema.json
|
||||
2adafd354ae5937ad282967d51960578694fc6530a168294bdb6e14bf36a735a schemas/publisher-unlock-v1.schema.json
|
||||
47713c8d80e58e9fe5f60c150151d5d99fae16dc62e5d5a49b33b8acac55e2c8 schemas/release-approval-payload-v1.schema.json
|
||||
1d623e3f6efe166e496641b9563a0d05107a6d4c4989b050fd4c4660362fbb3b schemas/release-root-descriptor-v1.schema.json
|
||||
947c9c8f9992a73e487b9d643f6d4ac9f167c36eda4978aec1ab2417647edb14 schemas/release-root-input-v1.schema.json
|
||||
658813b8a0096ab5ee66dd217dc0bbb1b08e09dc33241049f24190bb4df86c9d schemas/runtime-layout-v1.schema.json
|
||||
4f85f0a5e6bee1546a4ff731f26c1b536d4ba15ec9e8a07412041d7c6db314f4 schemas/task-scoped-release-site-v2.schema.json
|
||||
31bce5c2960d8c57b26e0fa854196c17e6f018cd547b58679b7877cb602df4f4 schemas/task-scoped-release-v5.schema.json
|
||||
f61a21e5927135fb26d2b81e233524387fdaa4436178e6c44df49a5bec1a3d2f src/aios_task_scoped_release/__init__.py
|
||||
935a1c1166b0c1ea35a82256345000bf2c73ded718d77773bc27a71ecce28f7d src/aios_task_scoped_release/__main__.py
|
||||
5da41d38d4dc37052aac983dd89a602b35d4bfe0c1cf07d268a5cfcdb5b03adf src/aios_task_scoped_release/acceptance.py
|
||||
d6954773080d441efad2ad6fd48e0b5af93ab955943a821da2cf972c1417f66c src/aios_task_scoped_release/action_broker.py
|
||||
19d9a002887ce63127efc92499e132d56d8ef5aa6bd8bc067f94f3f5369dd3d6 src/aios_task_scoped_release/action_client.py
|
||||
b8f52e528ba3b28531c4bcb221252731dee9558c07295567e3c8fc3e56dc1cbc src/aios_task_scoped_release/action_program.py
|
||||
b87425b7a1719821a212fb69345cafe0e70024fec1eec176b8e05889ff608ccc src/aios_task_scoped_release/anchor.py
|
||||
6dde00d8b501eb527bfc0532f895f39fb62466e5cc233ffa47d7ed8fa2e1c029 src/aios_task_scoped_release/anchor_service.py
|
||||
c591a9af33fcafed8f6b73789eb7095d1127ec22395e5eaba7cb088c4d2c6dd3 src/aios_task_scoped_release/approval_observer.py
|
||||
a8f62a2616cd53d68ae792f97c9c436ade76b9a587dab1f4187c2fef4ddc12e5 src/aios_task_scoped_release/artifact.py
|
||||
10dc0257acc6357d0d8bb27b9332d1e379bea8fda2d4d90d3b2586b974fcc51c src/aios_task_scoped_release/broker_passthrough.py
|
||||
2bb93ad8c492a8c5c94b6d9a47bdf4e1e6dc5729f79903c061495c434f38ceb7 src/aios_task_scoped_release/cache.py
|
||||
232ba8173c9bee19c8eec781347d2b0aaee6b675aee7d4286522f3276c9dbb9e src/aios_task_scoped_release/cli.py
|
||||
688d58a92788d619477bad24eb3890945a23bd7c58ee5305b9118d2964c03070 src/aios_task_scoped_release/command.py
|
||||
452aa7888faf7809e4be183931cd2d07e4e353a401abe2fe5c12aa9872326bda src/aios_task_scoped_release/common.py
|
||||
c78e8b8ad3b8f65b9c9c93656e556565de80c77db1cd39ea5c968b5800077c99 src/aios_task_scoped_release/contract.py
|
||||
21e18af44a839f035538de12be22aba0e7fa7e07853416fa6ec12b4c6d5bc6d6 src/aios_task_scoped_release/controller.py
|
||||
cb0e2e461a8c85a655652df9cfdab4487b49b8de9725d39265b676c9f9041a94 src/aios_task_scoped_release/errors.py
|
||||
03681a1a191cb0e2e1840351dadcc2ebc13f37a1185f003a11ac0bae31ecd324 src/aios_task_scoped_release/evidence_closure.py
|
||||
62a8aa163a37d0d0acc911e60b19a1aa5ccf37967b1d67960b62d3914d985633 src/aios_task_scoped_release/gitops.py
|
||||
83638ed6e23d2bed225c98acc0ea3740516e25c25bc997f611ecbebe629c57e3 src/aios_task_scoped_release/live_actions.py
|
||||
6937aec2d1526d9649d070b205bdcb712b5305cf9b9a88f0733507b22b1131b7 src/aios_task_scoped_release/metadata_action_handlers.py
|
||||
13a73feaee064fea70051e301145bcbb252c99e90cda9a4dbe5c1e8a9837ec5a src/aios_task_scoped_release/metadata_writer_runtime.py
|
||||
36949a793f4e933ddfc1571b30dd87e10cee267969a7e27eea72d5eec84181f3 src/aios_task_scoped_release/metadata_writer_server.py
|
||||
33e6832239e32e44ee133956e456ec2739c9a3b0468b91152dd718cf5e5d7793 src/aios_task_scoped_release/operation_service.py
|
||||
b7ef2cc96a4f183cc2afdcc09682331574c762c2216c800e9de4dcbf264d02a4 src/aios_task_scoped_release/release_root.py
|
||||
cf77b6c34345f281c9d40525ed88c781a758fd4bd97909bd8e8f1b545e44e13f src/aios_task_scoped_release/runtime_action_handlers.py
|
||||
047b6afb5c6048ed0e40af7169c3df7ada8771b3316c5d68d784a0fef13b0b09 src/aios_task_scoped_release/runtime_layout.py
|
||||
6cf6d36be915b882332d31b0e1f45a3c2ca754c0bbb0592338821923f107b550 src/aios_task_scoped_release/tag_object.py
|
||||
b852e0fb0f1fdc86b93cf4e8959ba8bf5d4f7935af483043a7ac23518825818e src/aios_task_scoped_release/unix.py
|
||||
dff1148041e76d83834e6a3988a439e2ac279142c2aab10e0c75bd08f9a682f1 tests/helpers.py
|
||||
4fab0f06edead1e21cad63188b377a97062721d56fb65efa08ae2bae0278c050 tests/test_acceptance.py
|
||||
b9deb424886a37e19963e8cef6f2b73f18f7f2f9a8a508325a35b00f5a697ddc tests/test_action_broker.py
|
||||
46ef68a61ce5653eb9625540e8e0f8bc40b1bd6085bc9a15e1301b6670bace35 tests/test_action_broker_posix.py
|
||||
5f1383e74d4864a186334180416fb3ecc27fc8a7c67b4b319b6c1fe65aa40e8e tests/test_action_program.py
|
||||
ceb3847ba92ea0a8296a3d2246ff937ba5816ababf26b56a3b8570250643e54f tests/test_anchor.py
|
||||
5785d33c698d88bdbd087d12c57b16b8e299ad537f687969a8186141725ae901 tests/test_anchor_service_roles.py
|
||||
4bdbe8c350c6c573fd859c5421f4bca3130c326626e8f2ddaad8070c44c24e57 tests/test_anchor_socket.py
|
||||
451a3bc0d23a59ec94970abdb6bed75b2ce1bd55493fc551bdf70d895cef7010 tests/test_approval_observer.py
|
||||
6bf08bcbcbac417c52d75f87fe80dc68355ac15c6c6e4c271b0046c1157b8561 tests/test_artifact.py
|
||||
16de82a79afd8df6e12c42a46e7a2e1d143dd6576baf34b00db5b24074b41567 tests/test_broker_passthrough.py
|
||||
2519730d2a95f9146ac092fb834959ee832b5091021d9d6c127577cb7c053135 tests/test_cache.py
|
||||
af85adf4b972b74ef7a20a2f4a3be04a2edbb88b9efb22aeacc5784aad9571ff tests/test_cli_module.py
|
||||
b8171e11a3e734938320eabe2239defe3b59a248b3bc71cacf1e6b5b568b97f1 tests/test_command.py
|
||||
9a5c1eea7d27b1fe81eb1eaf70695950e60ba5eecfb4a58d83b1212f0dbcbf4f tests/test_contract.py
|
||||
ab7725744907c4a16342aae14d3b34044eedf37472ed93bcf94865d2e65aa2b6 tests/test_controller.py
|
||||
6767ddd903a58b1b9c9736a5f23c2e1a669093421a45573c0e9552a91284ce94 tests/test_evidence_closure.py
|
||||
68b49e70d2b4a70910a7bdaf6f8f192c6b62ebd26de850e325a9057c95e9ca66 tests/test_gitops.py
|
||||
10e9b20ebe40ace2b8f576ddb1c847cf0b3b87a228a888928ee959783fdc2f49 tests/test_json_schemas.py
|
||||
83dd25df4f913a5a632406321bc703812c5059e91ec42e96ab4b979dec707e93 tests/test_live_actions.py
|
||||
6357424ff92ae7d19c8d545fdaa99a0c632d6e090ac38fa29593643fef8123b6 tests/test_metadata_action_handlers.py
|
||||
7e41a9003cda41a2b072dc0a0e39661d63c28f712555d03f5bcbb0d13e68bf9e tests/test_metadata_writer_runtime.py
|
||||
26d8fb90e29789ec9bf8de4582b1720cf1680305ae296079d2ff6605e1d71cf6 tests/test_metadata_writer_server.py
|
||||
6e69adf47acfc4dac9b1773bd78a88b7cba21430ed421fc2af9739c53cae1817 tests/test_operation_service.py
|
||||
c84f8f0ebed526f0fe176204c257b38caa0a57836fdb9e53ee038b07fa5a2e82 tests/test_release_root.py
|
||||
c4c73bfc1ab891a29e56b115b6b34d1726d5dae5d9b683d789b7ffec5b6e5637 tests/test_root_action_runner.py
|
||||
b2382bd57de48677ef61cea2cc8d17cf916b75d6215e9faa2f444c271f38c91a tests/test_runtime_action_handlers.py
|
||||
73201e1537d3e85fa78fbe8b737af87054a69fd5c6b19d6ffd62771e96da386a tests/test_runtime_layout.py
|
||||
235a7b93c6d66c58eba5a1bc23bdb884dda2d6815ea4584a47159bddf8145e60 tests/test_systemd_and_schema.py
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"compileall": "PASS",
|
||||
"git_fsck_connectivity_only": "PASS",
|
||||
"live_operations_not_performed": [
|
||||
"No AIOS or Gitea ref was written by package validation.",
|
||||
"No V5R1 service, account, token, credential or ACL was installed.",
|
||||
"No production, tag, Registry or Baseline object was changed."
|
||||
],
|
||||
"package": "AIOS-V2R1C4-TASK-SCOPED-RELEASE-V5R1",
|
||||
"package_revision": "V5R1",
|
||||
"package_version": "1.4.0",
|
||||
"platform": {
|
||||
"git": "2.43.0",
|
||||
"python": "3.12.3",
|
||||
"system": "Linux x86_64",
|
||||
"systemd": "255"
|
||||
},
|
||||
"runtime_dependencies": [],
|
||||
"schemas_validated": 17,
|
||||
"status": "PASS",
|
||||
"systemd_staged_root": {
|
||||
"deployment_definitions_sha256": "b1e7379b1d8cbc5c83b41fee278b2a85e115a6bd071f7e0670563659429b70c0",
|
||||
"services_verified": 7,
|
||||
"status": "PASS"
|
||||
},
|
||||
"tests": {
|
||||
"collection_errors": 0,
|
||||
"duration_seconds": 9.953,
|
||||
"failed": 0,
|
||||
"passed": 391,
|
||||
"skipped": 0,
|
||||
"suites": {
|
||||
"exact_two_parent_and_package_closure": 14,
|
||||
"task_scoped_release_control_plane": 377
|
||||
},
|
||||
"total": 391
|
||||
},
|
||||
"wheel": {
|
||||
"filename": "aios_task_scoped_release-1.4.0-py3-none-any.whl",
|
||||
"reproducible_two_builds": true,
|
||||
"sha256": "91c7452965eb668b8f696d83ce43950bfe61805aed29a161610c9ad12d0d7b19",
|
||||
"source_files_verified": 30
|
||||
},
|
||||
"zipapp": {
|
||||
"action_broker_sha256": "0a4833127697c8a7a4ed88c2b2c2433621ed9bb7a767047abcd2e0b8ec773558",
|
||||
"action_program_sha256": "245cda7c90f40a3c85d68e29bb927d367d8b405857dd6f235b547cbb3e9d8df8",
|
||||
"reproducible_two_builds": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"candidate_unit_read_only_binding": "PASS",
|
||||
"direct_main_push_implementation_present": false,
|
||||
"exact_two_parent_merge_scope": "governance/approvals",
|
||||
"external_absolute_symlink_policy": "DECLARED_ONLY",
|
||||
"formal_declaration_placeholder_scan": "PASS",
|
||||
"forbidden_git_hits": [],
|
||||
"force_push_implementation_present": false,
|
||||
"global_gitops_write_switch_required": false,
|
||||
"manifest_excludes_itself": true,
|
||||
"manifest_relative_sorted_unique_paths": "PASS",
|
||||
"package_revision": "V5R1",
|
||||
"package_zip_sha256_source": "EXTERNAL_SIDECAR",
|
||||
"path_traversal_entries": 0,
|
||||
"production_shell_hits": [],
|
||||
"production_shell_scan": "PASS",
|
||||
"runtime_dependencies": 0,
|
||||
"secret_hits": [],
|
||||
"secret_scan": "PASS",
|
||||
"source_symlinks": [],
|
||||
"stale_formal_digest_hits": [],
|
||||
"stale_runtime_path_hits": [],
|
||||
"status": "PASS",
|
||||
"systemd_v5_isolation": "PASS",
|
||||
"tag_rewrite_implementation_present": false,
|
||||
"zip_digest_self_reference": false
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
# V5R1 package closure
|
||||
|
||||
V5R1 is the successor packaging revision for the V2R1C4 task-scoped release
|
||||
control plane. It does not change the frozen product source, release artifact,
|
||||
rollback artifact, Release Root, Evidence Manifest, evidence closure, or tag
|
||||
object.
|
||||
|
||||
The archive is deterministic and non-self-referential:
|
||||
|
||||
- `MANIFEST.sha256` covers every other file in sorted relative-path order.
|
||||
- `MANIFEST.sha256` excludes itself.
|
||||
- symbolic links and special files are rejected.
|
||||
- `PACKAGE_COMPONENTS.json` declares
|
||||
`package_zip_sha256_source=EXTERNAL_SIDECAR`.
|
||||
- the completed ZIP digest exists only in the adjacent `-SHA256.txt` sidecar.
|
||||
|
||||
Validated fixed component digests:
|
||||
|
||||
```text
|
||||
wheel_sha256=91c7452965eb668b8f696d83ce43950bfe61805aed29a161610c9ad12d0d7b19
|
||||
deployment_definitions_sha256=b1e7379b1d8cbc5c83b41fee278b2a85e115a6bd071f7e0670563659429b70c0
|
||||
manifest_schema_sha256=31bce5c2960d8c57b26e0fa854196c17e6f018cd547b58679b7877cb602df4f4
|
||||
runtime_layout_schema_sha256=658813b8a0096ab5ee66dd217dc0bbb1b08e09dc33241049f24190bb4df86c9d
|
||||
exact_two_parent_broker_sha256=eb98a9ef64bbbc26e0c0ea9e6f1b7ef247ae685b96ed2b8c0edba7acc39fa4e4
|
||||
broker_schema_sha256=72efc595a85b471260110197ac845171c8447ab4edbf1ded38aed5bee6fe6aba
|
||||
validator_schema_sha256=95c23c5e54b0307fd1ab67dad92edaebdee6d4c495d8009c19015775f75d1f22
|
||||
site_config_sha256=be44861e717513e67b532fdb2f20b67a96aad07e212d521d4a69148ca346760c
|
||||
```
|
||||
|
||||
Linux verification completed with 391 passing tests, zero failures, zero
|
||||
skips, JSON Schema validation, `systemd-analyze verify`, real two-parent Git
|
||||
object creation, object-only import, `git fsck --connectivity-only`, and exact
|
||||
compare-and-swap ref updates in isolated repositories.
|
||||
|
||||
Files below `examples/` are non-production contract fixtures. Placeholder
|
||||
sentinels in those fixtures are rejected if copied into a formal declaration
|
||||
or production approval object.
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"action_broker_pyz_sha256": "0a4833127697c8a7a4ed88c2b2c2433621ed9bb7a767047abcd2e0b8ec773558",
|
||||
"action_program_pyz_sha256": "245cda7c90f40a3c85d68e29bb927d367d8b405857dd6f235b547cbb3e9d8df8",
|
||||
"broker_schema_sha256": "72efc595a85b471260110197ac845171c8447ab4edbf1ded38aed5bee6fe6aba",
|
||||
"deployment_definition_files": 22,
|
||||
"deployment_definitions_sha256": "b1e7379b1d8cbc5c83b41fee278b2a85e115a6bd071f7e0670563659429b70c0",
|
||||
"exact_two_parent_broker_sha256": "eb98a9ef64bbbc26e0c0ea9e6f1b7ef247ae685b96ed2b8c0edba7acc39fa4e4",
|
||||
"manifest_schema_sha256": "31bce5c2960d8c57b26e0fa854196c17e6f018cd547b58679b7877cb602df4f4",
|
||||
"package_version": "1.4.0",
|
||||
"package_revision": "V5R1",
|
||||
"package_zip_sha256_source": "EXTERNAL_SIDECAR",
|
||||
"runtime_layout_algorithm_id": "AIOS_RUNTIME_LAYOUT_V1",
|
||||
"runtime_layout_schema_sha256": "658813b8a0096ab5ee66dd217dc0bbb1b08e09dc33241049f24190bb4df86c9d",
|
||||
"schema_id": "AIOS_TASK_SCOPED_RELEASE_V5R1_COMPONENTS",
|
||||
"schema_version": 1,
|
||||
"systemd_runtime_directory": "aios-v2r1c4-release-v5",
|
||||
"systemd_state_directory_prefix": "aios-v2r1c4-release-v5",
|
||||
"site_config_sha256": "be44861e717513e67b532fdb2f20b67a96aad07e212d521d4a69148ca346760c",
|
||||
"tree_algorithm_id": "AIOS_TREE_SHA256_V1",
|
||||
"validator_schema_sha256": "95c23c5e54b0307fd1ab67dad92edaebdee6d4c495d8009c19015775f75d1f22",
|
||||
"wheel_file": "aios_task_scoped_release-1.4.0-py3-none-any.whl",
|
||||
"wheel_sha256": "91c7452965eb668b8f696d83ce43950bfe61805aed29a161610c9ad12d0d7b19"
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
# AIOS V2R1C4 Task-Scoped Release Control Plane V5R1
|
||||
|
||||
V5R1 is a packaging and governance-closure revision of V5. It retains the
|
||||
fixed product source, release Artifact, Rollback, Release Root, Evidence
|
||||
Manifest, and frozen Git object identities. It corrects stale component
|
||||
digest declarations and carries the separately governed exact two-parent
|
||||
merge contract without changing product code.
|
||||
|
||||
The ZIP digest is intentionally external. `PACKAGE_COMPONENTS.json` declares
|
||||
`package_zip_sha256_source=EXTERNAL_SIDECAR`; the actual digest is written only
|
||||
to the sidecar beside the completed archive. `MANIFEST.sha256` covers every
|
||||
other fixed package file, never itself or the external ZIP.
|
||||
|
||||
## V5 closure changes
|
||||
|
||||
- Safe Artifact extraction rejects path traversal, special files, and any
|
||||
undeclared absolute link before files are materialized.
|
||||
- The approved Artifact is copied through a digest-verifying file descriptor
|
||||
into a private snapshot before tar parsing. Its archive digest and generated
|
||||
extracted-file-manifest digest are both bound into the closure.
|
||||
- The one permitted absolute link is fixed to
|
||||
`vendor/openclaw-weixin/node_modules/openclaw -> /usr/lib/node_modules/openclaw`;
|
||||
mutable unit paths may not overlap that required read-only dependency.
|
||||
- Production-unit validation binds the candidate fragment bytes, requires an
|
||||
independently captured effective fragment with the same digest, and requires
|
||||
an explicit empty drop-in set.
|
||||
- The release Manifest uses task schema v5. The content-addressed
|
||||
runtime-layout document remains `AIOS_RUNTIME_LAYOUT_V1`.
|
||||
|
||||
## V5 systemd isolation
|
||||
|
||||
Every control-plane service uses V5-scoped paths, including:
|
||||
|
||||
```text
|
||||
RuntimeDirectory=aios-v2r1c4-release-v5
|
||||
RuntimeDirectoryPreserve=no
|
||||
StateDirectory=aios-v2r1c4-release-v5/<role>
|
||||
/run/aios-v2r1c4-release-v5
|
||||
/var/lib/aios-v2r1c4-release-v5/<role>
|
||||
/etc/aios/v2r1c4-release-v5
|
||||
/opt/aios/v2r1c4-release-v5
|
||||
```
|
||||
|
||||
All `ExecStart`, socket, environment, state, `ReadWritePaths`, and effective
|
||||
drop-in values must conform to this V5 namespace.
|
||||
|
||||
## Required commands and inputs
|
||||
|
||||
```bash
|
||||
aios-task-release artifact-extract \
|
||||
--archive <release-artifact.tar> \
|
||||
--destination <new-private-extraction-directory> \
|
||||
--manifest-output <new-artifact-file-manifest.json> \
|
||||
--expected-sha256 <release-artifact-sha256> \
|
||||
--expected-root <single-archive-root-name> \
|
||||
--declared-absolute-symlink \
|
||||
vendor/openclaw-weixin/node_modules/openclaw=/usr/lib/node_modules/openclaw
|
||||
|
||||
aios-task-release runtime-layout-generate \
|
||||
--input runtime-layout.input.json \
|
||||
--output runtime-layout.json
|
||||
|
||||
aios-task-release runtime-layout-verify \
|
||||
--manifest runtime-layout.json \
|
||||
--release-artifact <release-artifact.tar> \
|
||||
--artifact-root <safe-extracted-artifact-root> \
|
||||
--production-unit <approved-production-unit-fragment> \
|
||||
--effective-production-unit <captured-effective-fragment> \
|
||||
--no-unit-drop-ins \
|
||||
--expected-sha256 <runtime-layout-sha256>
|
||||
```
|
||||
|
||||
The production cache gate requires both unit paths and an explicit empty
|
||||
drop-in list. Deployment evidence must independently bind the installed
|
||||
fragment path and digest and report no resolved drop-ins. Use
|
||||
`AGENT_EXECUTION_REQUIREMENTS_V5.md` as the production execution entry point.
|
||||
|
||||
## Approved execution order
|
||||
|
||||
The Phase B approval payload binds this exact state-machine sequence:
|
||||
|
||||
```text
|
||||
record_main_reconciliation -> import_exact_objects -> publish_exact_branch
|
||||
-> deploy_candidate (isolated candidate only; production unchanged)
|
||||
-> migrate_owner_identity -> promote_production -> scan_core_metadata
|
||||
-> record_fresh_acceptance -> publish_exact_tag -> write_release_registry
|
||||
-> write_production_baseline -> revoke_task_credentials -> complete
|
||||
```
|
||||
|
||||
`promote_production` is an independent, receipt-producing operation. A
|
||||
candidate-deployment result containing a production-promotion receipt is
|
||||
rejected. The registry and baseline actions each consume one Anchor quota and
|
||||
must perform their complete C3 then C4 sequence as read, one governed
|
||||
`/api/commit-file` write, bounded visibility wait, and reread for each stage.
|
||||
The C3 stage must bind its own historical Artifact and rollback digests; the
|
||||
C4 digests are never accepted as C3 evidence.
|
||||
|
||||
Every entry in `operation_sequence`, including Anchor-only actions and
|
||||
`complete`, has one mandatory `approved_action_plans` object. The plan fixes
|
||||
absolute paths, OIDs, refs, SHA-256 bindings, URL hosts, actor identity, and
|
||||
read/write order. The object deliberately has no self-referential digest.
|
||||
Instead its canonical SHA-256 must be identical in the approved Manifest,
|
||||
approval payload, and operation request field `action_plan_sha256`; it is also
|
||||
included in the Anchor reservation request digest (or the corresponding atomic
|
||||
fresh-acceptance/complete Anchor request). For the eight root-brokered actions,
|
||||
`config.actions[action].plan_sha256` and the broker payload add a second
|
||||
three-way boundary. Direct and Anchor-only actions deliberately have no fake
|
||||
broker config entry. Any mismatch fails before reservation or execution.
|
||||
Gitea API reads are fixed to `127.0.0.1`, while Git pushes are fixed to
|
||||
`g.cnnetmkt.top`.
|
||||
|
||||
Production wrappers receive an exact `execute=true` request constructed by the
|
||||
operation service from the approved Manifest and Anchor idempotency key. They
|
||||
cannot fall back to PLAN mode. The wrappers are thin AF_UNIX clients of
|
||||
`/run/aios-v2r1c4-release-v5/root-action-broker.sock`; they validate framing
|
||||
and the returned evidence digest but do not execute production logic. The
|
||||
production path is `broker_passthrough -> root action broker -> pinned action
|
||||
program`; `live_actions` is not the production execution path.
|
||||
|
||||
The root broker rejects the former three-field request, authenticates the peer
|
||||
role, validates the plan digest, and injects the startup-pinned action-program
|
||||
and config digests into the strict child request. It also requires an already
|
||||
open Anchor reservation for the same authorization, action and idempotency key,
|
||||
and requires the Anchor campaign to be in the action's exact predecessor state.
|
||||
The broker never reserves or advances Anchor. Required predecessors are read
|
||||
from the Anchor snapshot, validated as full committed PASS envelopes, encoded
|
||||
by the root broker into a mode-0400 sealed memfd, and passed to the
|
||||
privilege-dropped child by descriptor. Caller-supplied predecessor evidence is
|
||||
rejected and is never forwarded by the wrapper.
|
||||
|
||||
Anchor terminal evidence is not a bare digest. Both PASS and FAIL envelopes
|
||||
have exactly these fields:
|
||||
|
||||
```text
|
||||
authorization_id, manifest_sha256, operation,
|
||||
producer_action_plan_sha256, idempotency_key, status, payload,
|
||||
evidence_sha256, chain_hash
|
||||
```
|
||||
|
||||
`evidence_sha256` is recomputed over the canonical first seven fields. PASS
|
||||
envelopes appear only in `committed_operations`; FAIL envelopes appear only in
|
||||
`failed_operations`. A FAIL releases the reservation quota but does not advance
|
||||
the campaign state. Reusing a terminal idempotency key with different evidence
|
||||
is a conflict.
|
||||
|
||||
The two quarantine roots are intentionally separate: Main reconciliation uses
|
||||
the root broker's exact ephemeral
|
||||
`/run/aios-v2r1c4-release-v5/quarantine/main-reconciliation.git`, while the
|
||||
writer object-import workflow retains its site-config-scoped
|
||||
`/var/lib/aios-v2r1c4-release-v5/quarantine`. Neither path may substitute for
|
||||
the other.
|
||||
|
||||
## Digest status
|
||||
|
||||
V4 component-digest assertions do not apply to V5. Final V5 package/component
|
||||
digests, manifests and test totals are generated and recorded only by the
|
||||
closure rebuild. No future fresh-acceptance evidence path or digest is fixed in
|
||||
advance. The approved object is the content-addressed fresh-acceptance
|
||||
derivation contract; the evidence digest is created only after a new natural
|
||||
Owner event is independently observed and atomically consumed by Anchor. Fixed
|
||||
product source and input-artifact hashes remain unchanged.
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
# Recovery and Reconciliation V5
|
||||
|
||||
## Runtime-layout drift
|
||||
|
||||
If runtime-layout verification fails:
|
||||
|
||||
- do not modify the approved Manifest;
|
||||
- do not create a drop-in;
|
||||
- do not replace the external dependency in place;
|
||||
- do not continue to Release Root generation.
|
||||
|
||||
Recalculate actual values and determine whether the failure is transient
|
||||
extraction damage or a real object change. A real object change requires a
|
||||
new refreeze and approval.
|
||||
|
||||
## systemd isolation drift
|
||||
|
||||
If any V5 unit creates an unversioned or V3 path, stop before installation.
|
||||
Only a rebuilt deployment-definition set is acceptable. Runtime drop-ins are
|
||||
not a substitute for content-addressed unit definitions.
|
||||
|
||||
## Side-effect recovery
|
||||
|
||||
Every consequential operation uses an Anchor reservation.
|
||||
|
||||
- committed idempotency keys return the existing evidence digest;
|
||||
- an open reservation requires direct observation and reconciliation;
|
||||
- a failed reservation releases its quota reservation;
|
||||
- a key cannot be reused with another request digest.
|
||||
|
||||
Reconciliation reads the full Anchor snapshot. A committed operation must have
|
||||
a PASS envelope and a failed operation must have a FAIL envelope, each with the
|
||||
exact authorization, Manifest digest, operation, producer action-plan digest,
|
||||
idempotency key, payload, recomputable evidence SHA-256 and chain hash. Missing,
|
||||
cross-state or conflicting evidence is corruption, not a retry signal.
|
||||
|
||||
The root broker never repairs a missing reservation. It requires the existing
|
||||
reservation and exact campaign state, then obtains committed predecessors from
|
||||
Anchor itself. Do not place predecessor evidence in a retry request; the
|
||||
passthrough and broker reject caller-supplied copies.
|
||||
|
||||
Before retrying or beginning any production switch, repeat live rollback
|
||||
verification: current production active and healthy, rollback archive present
|
||||
and readable with the approved SHA-256, and rollback worktree at the approved
|
||||
commit and tree. If promotion fails, verify C3 restoration and health. Report
|
||||
`PROMOTION_FAILED_ROLLBACK_UNVERIFIED` when restoration cannot be proved; do
|
||||
not continue from nominal service state.
|
||||
|
||||
Fresh acceptance is reconciled from Anchor's atomic consumption record and its
|
||||
committed PASS envelope. The approved derivation contract is stable; the
|
||||
concrete event/evidence digest is necessarily runtime data and must not be
|
||||
replaced by a pre-created future evidence file or digest.
|
||||
|
||||
Same-name different-OID branches or Tags require manual Owner review. No
|
||||
force push, Tag rewrite or direct `main` write is permitted.
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
# V2R1C4 Release Refreeze Workflow V5
|
||||
|
||||
## Reusable immutable objects
|
||||
|
||||
```text
|
||||
source_commit_oid=1857b88690cd1c0119df984534c260b6f217a21a
|
||||
source_tree_oid=17c153de2338f570f70cb7407e7dda3c596b524b
|
||||
|
||||
release_artifact_sha256=
|
||||
39523caa48f39b33e5e5df785bb568e0381d8b20301746c280442f73f294f2b2
|
||||
|
||||
rollback_artifact_sha256=
|
||||
dd0dbbe21db14722996636347ea1d22917436c688d5f0b7055b5bfb1776c4d95
|
||||
```
|
||||
|
||||
Product code must remain byte-identical.
|
||||
|
||||
## Refreeze sequence
|
||||
|
||||
1. verify the V5 package and all six V5 systemd services;
|
||||
2. safely extract the fixed Artifact;
|
||||
3. calculate the external OpenClaw package and Tree digests;
|
||||
4. generate the exact candidate production unit;
|
||||
5. generate and verify `runtime-layout.json`;
|
||||
6. put the runtime-layout digest into `release-root.input.json`;
|
||||
7. generate and verify `release-root.manifest.json`;
|
||||
8. generate and verify `evidence-manifest.json`;
|
||||
9. create one new evidence closure commit directly on the fixed source;
|
||||
10. compute the new evidence tree and closure OIDs;
|
||||
11. build and verify a connected Git Bundle;
|
||||
12. compute a deterministic annotated V2R1C4 Tag object;
|
||||
13. populate the complete V5 release Manifest;
|
||||
14. request one exact machine-readable Owner approval.
|
||||
|
||||
Do not generate an Owner approval payload before all V5 control-plane,
|
||||
runtime-layout, Release Root, evidence, Git and Tag objects are frozen.
|
||||
@@ -0,0 +1,93 @@
|
||||
# Runtime Layout Closure V5
|
||||
|
||||
## Required real inputs
|
||||
|
||||
The Agent must safely extract the fixed Release Artifact without following
|
||||
links, then enumerate all entries using `lstat`.
|
||||
|
||||
Expected absolute link:
|
||||
|
||||
```text
|
||||
artifact_path=vendor/openclaw-weixin/node_modules/openclaw
|
||||
link_target=/usr/lib/node_modules/openclaw
|
||||
```
|
||||
|
||||
No other absolute Artifact link may exist.
|
||||
|
||||
For `/usr/lib/node_modules/openclaw`, compute:
|
||||
|
||||
```text
|
||||
package_json_sha256=<full SHA-256>
|
||||
package_name=openclaw
|
||||
package_version=2026.6.11
|
||||
target_tree_sha256=<AIOS_TREE_SHA256_V1>
|
||||
```
|
||||
|
||||
Do not accept a prefix-only digest.
|
||||
|
||||
## Tree algorithm
|
||||
|
||||
`AIOS_TREE_SHA256_V1` hashes a canonical ordered list of:
|
||||
|
||||
- relative path;
|
||||
- entry type;
|
||||
- permission mode;
|
||||
- regular-file size and SHA-256;
|
||||
- relative symlink target.
|
||||
|
||||
The algorithm excludes timestamps, UID and GID. It rejects:
|
||||
|
||||
- devices;
|
||||
- sockets;
|
||||
- FIFOs;
|
||||
- nested absolute symlinks;
|
||||
- relative symlinks escaping the dependency root.
|
||||
|
||||
## Candidate production unit
|
||||
|
||||
Before Owner approval, generate the exact candidate production unit and
|
||||
bind its complete SHA-256.
|
||||
|
||||
The unit must contain:
|
||||
|
||||
```text
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ReadOnlyPaths=/usr/lib/node_modules/openclaw
|
||||
```
|
||||
|
||||
`BindReadOnlyPaths=/usr/lib/node_modules/openclaw` is also accepted. A unit
|
||||
drop-in not included in the approved runtime-layout digest is forbidden.
|
||||
|
||||
## Verification result
|
||||
|
||||
A passing result contains:
|
||||
|
||||
```text
|
||||
status=PASS
|
||||
absolute_symlink_count=1
|
||||
external_dependency.package_version=2026.6.11
|
||||
external_dependency.read_only_binding=PASS
|
||||
production_unit_sha256=<approved value>
|
||||
target_tree_sha256=<approved value>
|
||||
```
|
||||
|
||||
## Control-plane runtime ownership
|
||||
|
||||
The runtime layout must preserve these exact identities:
|
||||
|
||||
```text
|
||||
aios-v2r1c4-task-anchor
|
||||
aios-v2r1c4-approval-observer
|
||||
aios-v2r1c4-task-writer
|
||||
aios-v2r1c4-task-controller
|
||||
aios-v2r1c4-task-publisher
|
||||
root # root action broker only
|
||||
```
|
||||
|
||||
All non-root roles use group `aios-v2r1c4-release`; the root action broker uses
|
||||
`root:root`. The Writer unit hosts both its AF_UNIX operation socket and the
|
||||
loopback metadata Writer at `127.0.0.1:19892`. Only that unit receives the
|
||||
metadata API bearer and Gitea publication credentials. The root action broker
|
||||
socket is `/run/aios-v2r1c4-release-v5/root-action-broker.sock`; action wrappers
|
||||
are thin AF_UNIX clients and do not provide a second production implementation.
|
||||
@@ -0,0 +1,22 @@
|
||||
939495d29782d1f7c063131dcd134c31df0ad07079ab15df99c5886efb3705a6 deploy/actions/action-config.schema.json
|
||||
413313a03547372988ec871ff30d3fabe767d1c6af13f7f1392c71e7251d8196 deploy/actions/aios-v2r1c4-root-action-runner
|
||||
162d87f32aa21aed794f89a582a67532415980eed9161781bb6618c5cf88b178 deploy/actions/deploy-candidate
|
||||
70aaf4febf1c94674c64cef534b15ff0d314e0eb988c1bb7604038c8619a71fb deploy/actions/migrate-owner-identity
|
||||
232fa6e7737e2d505afe8ed066f6dac6098c40cd6c3092dc56c8374c8f771f1f deploy/actions/promote-production
|
||||
97b51f4daf26e07380b0f4f57d8cf8e449da1ec5bcbf1f134d52d53b3aa4c7f0 deploy/actions/revoke-task-credentials
|
||||
8d26b43a886477a130d249825a0dba21ad29d0708332caf2ce1e68605fa20352 deploy/actions/scan-core-metadata
|
||||
1baa7571b2ff22c60a2ca8b7174e7f22615c048d753793b28c0ebdd93da903ba deploy/actions/write-production-baseline
|
||||
a239b785a9ddd5975ae10d9820b6d690d161b37e67c291d908f3ee9646db4983 deploy/actions/write-release-registry
|
||||
c5d20a1c90a713b5d17f95638cfec55b84dbeb05122d5269485d8f70fbc598d5 deploy/bin/aios-task-release
|
||||
437f8826bf84ba8e3876bd12f2ccc274d1f498424c08b6a44b1e2e0963a1ece7 deploy/bin/aios-task-release-anchor
|
||||
5f4f83c850240acf8bea951408911dd032b201c8ba60cbc912eaf54ba7d52f5c deploy/bin/aios-task-release-finalizer
|
||||
5a5ec858035ce59b519b12018f881301d4ff59d281bab840413f4f88b0a4e435 deploy/bin/aios-task-release-operation
|
||||
dc99c98da6ed5cd4af5a5a2c5a64daf96eefeda39497fd3bd2beba2c76b5846c deploy/bin/aios-task-release-revoker
|
||||
ca76dd130f0969fcbb5418c4276d4fbbe0671b340ffb2c615098a9989064a8fd deploy/systemd/aios-exact-two-parent-merge-broker@.service
|
||||
8f564cf7658b1f27666216e57de133677251c9fec298c04444b4b2764a4c2d49 deploy/systemd/aios-v2r1c4-release-anchor-v5.service
|
||||
e9cf179a5cc75d1dfa644698abad7991acec582fd988ac73f01ba47b37595325 deploy/systemd/aios-v2r1c4-release-approval-observer-v5.service
|
||||
d1f42014f73a64aa4662d4071a0ccc5523131bbac2b72267548d548d12aba1d8 deploy/systemd/aios-v2r1c4-release-controller-v5.service
|
||||
fdee4f640bac52d4e85806ad9681c7ec5b7ab695326232308d156c65b5e078be deploy/systemd/aios-v2r1c4-release-publisher-v5.service
|
||||
138f811d5f4694cb9603718fadfcf6ce8041c348905812f0b0d48cfdae905f51 deploy/systemd/aios-v2r1c4-release-writer-v5.service
|
||||
5dc58ac4a4f724c276be9f914e76f5ff17ab0eed13086e1c594e2237bfc8c859 deploy/systemd/aios-v2r1c4-root-action-broker.service
|
||||
440ee7ccac9da3861c04cfaa22768a11c4905f2874d2d776adbdd499de29ef22 deploy/tmpfiles.d/aios-v2r1c4-task-release-v5.conf
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "AIOS V2R1C4 descriptor-pinned root action broker configuration",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["manifest_sha256", "interpreter", "action_program", "anchor_socket", "anchor_timeout_seconds", "actions"],
|
||||
"properties": {
|
||||
"manifest_sha256": {
|
||||
"description": "SHA-256 of AIOS_CANONICAL_JSON_V1 bytes for the pinned release manifest.",
|
||||
"type": "string",
|
||||
"pattern": "^[0-9a-f]{64}$"
|
||||
},
|
||||
"interpreter": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["path", "sha256"],
|
||||
"properties": {
|
||||
"path": {"const": "/usr/bin/python3.12"},
|
||||
"sha256": {"const": "e1efa562c2cc2e35521a5c9c9b9939921001ff8ca9708a13ef15ace68cc2ccd7"}
|
||||
}
|
||||
},
|
||||
"action_program": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["path", "sha256"],
|
||||
"properties": {
|
||||
"path": {
|
||||
"const": "/opt/aios/v2r1c4-release-v5/libexec/action-program.pyz"
|
||||
},
|
||||
"sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"}
|
||||
}
|
||||
},
|
||||
"anchor_socket": {
|
||||
"description": "Fixed Anchor AF_UNIX endpoint used by the root broker through a caller-UID-dropped snapshot child.",
|
||||
"const": "/run/aios-v2r1c4-release-v5/anchor.sock"
|
||||
},
|
||||
"anchor_timeout_seconds": {
|
||||
"type": "number",
|
||||
"exclusiveMinimum": 0,
|
||||
"maximum": 30
|
||||
},
|
||||
"request_timeout_seconds": {
|
||||
"type": "number",
|
||||
"exclusiveMinimum": 0,
|
||||
"maximum": 30
|
||||
},
|
||||
"socket_gid": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 2147483647
|
||||
},
|
||||
"socket_parent_uid": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 2147483647
|
||||
},
|
||||
"environment": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"LANG": {"type": "string", "minLength": 1},
|
||||
"LC_ALL": {"type": "string", "minLength": 1},
|
||||
"PATH": {"type": "string", "minLength": 1},
|
||||
"TZ": {"type": "string", "minLength": 1}
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"record_main_reconciliation",
|
||||
"deploy_candidate",
|
||||
"migrate_owner_identity",
|
||||
"promote_production",
|
||||
"scan_core_metadata",
|
||||
"write_release_registry",
|
||||
"write_production_baseline",
|
||||
"revoke_task_credentials"
|
||||
],
|
||||
"properties": {
|
||||
"record_main_reconciliation": {"$ref": "#/$defs/action"},
|
||||
"deploy_candidate": {"$ref": "#/$defs/action"},
|
||||
"migrate_owner_identity": {"$ref": "#/$defs/action"},
|
||||
"promote_production": {"$ref": "#/$defs/action"},
|
||||
"scan_core_metadata": {"$ref": "#/$defs/action"},
|
||||
"write_release_registry": {"$ref": "#/$defs/action"},
|
||||
"write_production_baseline": {"$ref": "#/$defs/action"},
|
||||
"revoke_task_credentials": {"$ref": "#/$defs/action"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"action": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"plan_sha256",
|
||||
"caller_uid",
|
||||
"caller_gid",
|
||||
"exec_uid",
|
||||
"exec_gid",
|
||||
"timeout_seconds",
|
||||
"stdout_limit_bytes",
|
||||
"stderr_limit_bytes"
|
||||
],
|
||||
"properties": {
|
||||
"plan_sha256": {
|
||||
"description": "SHA-256 of the canonical approved plan embedded in the release manifest; the plan object itself must not contain this digest.",
|
||||
"type": "string",
|
||||
"pattern": "^[0-9a-f]{64}$"
|
||||
},
|
||||
"caller_uid": {"type": "integer", "minimum": 0, "maximum": 2147483647},
|
||||
"caller_gid": {"type": "integer", "minimum": 0, "maximum": 2147483647},
|
||||
"exec_uid": {"type": "integer", "minimum": 0, "maximum": 2147483647},
|
||||
"exec_gid": {"type": "integer", "minimum": 0, "maximum": 2147483647},
|
||||
"timeout_seconds": {
|
||||
"type": "number",
|
||||
"exclusiveMinimum": 0,
|
||||
"maximum": 300
|
||||
},
|
||||
"stdout_limit_bytes": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 1048576
|
||||
},
|
||||
"stderr_limit_bytes": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 1048576
|
||||
},
|
||||
"anchored_predecessor_evidence_required": {
|
||||
"description": "Must be true for promote_production and scan_core_metadata; the broker injects Anchor-owned evidence through a protected descriptor, never caller JSON.",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
exec /usr/bin/python3.12 /opt/aios/v2r1c4-release-v5/libexec/action-broker.pyz \
|
||||
--config /etc/aios/v2r1c4-release-v5/root-action-config.json \
|
||||
--manifest /etc/aios/v2r1c4-release-v5/release-manifest.json \
|
||||
--python /usr/bin/python3.12 \
|
||||
--program /opt/aios/v2r1c4-release-v5/libexec/action-program.pyz \
|
||||
--socket /run/aios-v2r1c4-release-v5/root-action-broker.sock
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
exec /usr/bin/python3.12 -m aios_task_scoped_release.broker_passthrough --action deploy_candidate "$@"
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
exec /usr/bin/python3.12 -m aios_task_scoped_release.broker_passthrough --action migrate_owner_identity "$@"
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
exec /usr/bin/python3.12 -m aios_task_scoped_release.broker_passthrough --action promote_production "$@"
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
exec /usr/bin/python3.12 -m aios_task_scoped_release.broker_passthrough --action revoke_task_credentials "$@"
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
exec /usr/bin/python3.12 -m aios_task_scoped_release.broker_passthrough --action scan_core_metadata "$@"
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
exec /usr/bin/python3.12 -m aios_task_scoped_release.broker_passthrough --action write_production_baseline "$@"
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
exec /usr/bin/python3.12 -m aios_task_scoped_release.broker_passthrough --action write_release_registry "$@"
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env python3
|
||||
from aios_task_scoped_release.cli import main
|
||||
raise SystemExit(main())
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env python3
|
||||
from aios_task_scoped_release.anchor_service import main
|
||||
raise SystemExit(main())
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Post-COMPLETE root finalizer for V2R1C4 task-scoped identities."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import sqlite3
|
||||
import stat
|
||||
import subprocess
|
||||
|
||||
|
||||
MANIFEST_PATH = "/etc/aios/v2r1c4-release-v5/release-manifest.json"
|
||||
ANCHOR_DATABASE = "/var/lib/aios-v2r1c4-release-v5/anchor/release.db"
|
||||
UNITS = (
|
||||
"aios-v2r1c4-release-publisher-v5.service",
|
||||
"aios-v2r1c4-release-writer-v5.service",
|
||||
"aios-v2r1c4-release-controller-v5.service",
|
||||
"aios-v2r1c4-release-approval-observer-v5.service",
|
||||
"aios-v2r1c4-release-anchor-v5.service",
|
||||
"aios-v2r1c4-root-action-broker.service",
|
||||
)
|
||||
ACCOUNTS = (
|
||||
("aios-v2r1c4-task-anchor", 61200),
|
||||
("aios-v2r1c4-task-writer", 61201),
|
||||
("aios-v2r1c4-task-publisher", 61202),
|
||||
("aios-v2r1c4-task-controller", 61203),
|
||||
("aios-v2r1c4-approval-observer", 61204),
|
||||
)
|
||||
PROJECTIONS = (
|
||||
"/run/credentials/aios-v2r1c4-release-approval-observer-v5.service/token",
|
||||
"/run/credentials/aios-v2r1c4-release-writer-v5.service/token",
|
||||
"/run/credentials/aios-v2r1c4-release-writer-v5.service/metadata-api-bearer",
|
||||
"/run/credentials/aios-v2r1c4-release-writer-v5.service/gitea-publish-token",
|
||||
"/run/credentials/aios-v2r1c4-release-publisher-v5.service/token",
|
||||
"/run/credentials/aios-v2r1c4-root-action-broker.service/gitea-api-token",
|
||||
"/run/credentials/aios-v2r1c4-root-action-broker.service/owner-id",
|
||||
"/run/credentials/aios-v2r1c4-root-action-broker.service/metadata-writer-token",
|
||||
"/run/credentials/aios-v2r1c4-root-action-broker.service/revoker-token",
|
||||
"/run/credentials/aios-v2r1c4-root-action-broker.service/old-token-0",
|
||||
"/run/credentials/aios-v2r1c4-root-action-broker.service/old-token-1",
|
||||
)
|
||||
REVOKER = "/opt/aios/v2r1c4-release-v5/bin/aios-task-release-revoker"
|
||||
|
||||
|
||||
class FinalizerError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def _read_root_file(path: str, maximum: int) -> bytes:
|
||||
flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0)
|
||||
try:
|
||||
fd = os.open(path, flags)
|
||||
try:
|
||||
metadata = os.fstat(fd)
|
||||
if (
|
||||
metadata.st_uid != 0
|
||||
or not stat.S_ISREG(metadata.st_mode)
|
||||
or stat.S_IMODE(metadata.st_mode) & 0o022
|
||||
):
|
||||
raise FinalizerError("FINALIZER_INPUT_INVALID")
|
||||
payload = os.read(fd, maximum + 1)
|
||||
finally:
|
||||
os.close(fd)
|
||||
except FinalizerError:
|
||||
raise
|
||||
except OSError:
|
||||
raise FinalizerError("FINALIZER_INPUT_INVALID") from None
|
||||
if len(payload) > maximum:
|
||||
raise FinalizerError("FINALIZER_INPUT_INVALID")
|
||||
return payload
|
||||
|
||||
|
||||
class LiveServices:
|
||||
def anchor_state(self) -> str:
|
||||
try:
|
||||
manifest = json.loads(_read_root_file(MANIFEST_PATH, 4 * 1024 * 1024))
|
||||
authorization_id = manifest["authorization_id"]
|
||||
except Exception:
|
||||
raise FinalizerError("ANCHOR_STATE_UNOBSERVED") from None
|
||||
if not isinstance(authorization_id, str) or not authorization_id:
|
||||
raise FinalizerError("ANCHOR_STATE_UNOBSERVED")
|
||||
try:
|
||||
connection = sqlite3.connect(
|
||||
f"file:{ANCHOR_DATABASE}?mode=ro", uri=True, timeout=5
|
||||
)
|
||||
try:
|
||||
row = connection.execute(
|
||||
"SELECT state FROM campaigns WHERE authorization_id=?",
|
||||
(authorization_id,),
|
||||
).fetchone()
|
||||
finally:
|
||||
connection.close()
|
||||
except sqlite3.Error:
|
||||
raise FinalizerError("ANCHOR_STATE_UNOBSERVED") from None
|
||||
if row is None or len(row) != 1 or not isinstance(row[0], str):
|
||||
raise FinalizerError("ANCHOR_STATE_UNOBSERVED")
|
||||
return row[0]
|
||||
|
||||
def writes_enabled(self) -> bool:
|
||||
return os.environ.get("AIOS_GITOPS_WRITES_ENABLED", "false").lower() == "true"
|
||||
|
||||
def command(self, argv):
|
||||
try:
|
||||
return subprocess.run(
|
||||
list(argv),
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
shell=False,
|
||||
cwd="/",
|
||||
env={"LANG": "C", "LC_ALL": "C", "PATH": "/usr/bin:/usr/sbin"},
|
||||
timeout=120,
|
||||
check=False,
|
||||
)
|
||||
except Exception:
|
||||
raise FinalizerError("FINALIZER_COMMAND_FAILED") from None
|
||||
|
||||
def account_uid(self, name: str):
|
||||
import pwd
|
||||
try:
|
||||
return pwd.getpwnam(name).pw_uid
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def uid_name(self, uid: int):
|
||||
import pwd
|
||||
try:
|
||||
return pwd.getpwuid(uid).pw_name
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def projection_exists(self, path: str) -> bool:
|
||||
return os.path.lexists(path)
|
||||
|
||||
def executable_sha256(self) -> str:
|
||||
with open(__file__, "rb") as stream:
|
||||
return hashlib.sha256(stream.read()).hexdigest()
|
||||
|
||||
|
||||
def _run_ok(services, argv, allowed=(0,)):
|
||||
result = services.command(argv)
|
||||
if result.returncode not in allowed:
|
||||
raise FinalizerError("FINALIZER_COMMAND_FAILED")
|
||||
return result
|
||||
|
||||
|
||||
def _finalize(services) -> dict[str, object]:
|
||||
if services.anchor_state() != "COMPLETE":
|
||||
raise FinalizerError("ANCHOR_NOT_COMPLETE")
|
||||
if services.writes_enabled() is not False:
|
||||
raise FinalizerError("GITOPS_WRITES_ENABLED")
|
||||
for unit in UNITS:
|
||||
_run_ok(services, ("/usr/bin/systemctl", "disable", "--now", unit))
|
||||
for unit in UNITS:
|
||||
_run_ok(
|
||||
services,
|
||||
("/usr/bin/systemctl", "is-active", "--quiet", unit),
|
||||
allowed=(3,),
|
||||
)
|
||||
_run_ok(
|
||||
services,
|
||||
("/usr/bin/systemctl", "is-enabled", "--quiet", unit),
|
||||
allowed=(1,),
|
||||
)
|
||||
if any(services.projection_exists(path) for path in PROJECTIONS):
|
||||
raise FinalizerError("CREDENTIAL_PROJECTIONS_REMAIN")
|
||||
for name, uid in ACCOUNTS:
|
||||
observed_uid = services.account_uid(name)
|
||||
observed_name = services.uid_name(uid)
|
||||
if observed_uid is None and observed_name is None:
|
||||
continue
|
||||
if observed_uid != uid or observed_name != name:
|
||||
raise FinalizerError("TASK_IDENTITY_BINDING_MISMATCH")
|
||||
_run_ok(services, ("/usr/sbin/userdel", "--", name))
|
||||
for name, uid in ACCOUNTS:
|
||||
if services.account_uid(name) is not None or services.uid_name(uid) is not None:
|
||||
raise FinalizerError("TASK_IDENTITY_REMAINS")
|
||||
_run_ok(services, ("/usr/sbin/groupdel", "aios-v2r1c4-release"), allowed=(0, 6))
|
||||
_run_ok(services, (REVOKER, "observe", "acl"), allowed=(1,))
|
||||
evidence: dict[str, object] = {
|
||||
"status": "PASS",
|
||||
"action": "post_complete_task_cleanup",
|
||||
"anchor_state": "COMPLETE",
|
||||
"temporary_accounts_remaining": 0,
|
||||
"temporary_services_remaining": 0,
|
||||
"credential_projections_remaining": 0,
|
||||
"task_acls_remaining": 0,
|
||||
"gitops_writes_enabled": False,
|
||||
"finalizer_sha256": services.executable_sha256(),
|
||||
}
|
||||
canonical = json.dumps(
|
||||
evidence, sort_keys=True, separators=(",", ":"), ensure_ascii=False
|
||||
).encode("utf-8")
|
||||
evidence["evidence_sha256"] = hashlib.sha256(canonical).hexdigest()
|
||||
return evidence
|
||||
|
||||
|
||||
def main(argv=None, *, services=None, emit=print) -> int:
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
try:
|
||||
parser.parse_args(argv)
|
||||
evidence = _finalize(services if services is not None else LiveServices())
|
||||
except FinalizerError as error:
|
||||
return 3 if str(error) == "ANCHOR_NOT_COMPLETE" else 2
|
||||
emit(json.dumps(evidence, sort_keys=True, separators=(",", ":")))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env python3
|
||||
from aios_task_scoped_release.operation_service import main
|
||||
raise SystemExit(main())
|
||||
+291
@@ -0,0 +1,291 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fixed, non-secret-bearing cleanup adapter for V2R1C4 task resources."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import stat
|
||||
import subprocess
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
GITEA_API_BASE = "http://127.0.0.1:3000/api/v1"
|
||||
REVOKER_CREDENTIAL_PATH = (
|
||||
"/run/credentials/aios-v2r1c4-root-action-broker.service/revoker-token"
|
||||
)
|
||||
TASK_TOKENS = (
|
||||
("aios-v2r1c4-task-writer", "aios-v2r1c4-task-writer-v5"),
|
||||
("aios-v2r1c4-task-publisher", "aios-v2r1c4-task-publisher-v5"),
|
||||
)
|
||||
TASK_ACCOUNTS = {
|
||||
"writer": "aios-v2r1c4-task-writer",
|
||||
"publisher": "aios-v2r1c4-task-publisher",
|
||||
}
|
||||
MAX_RESPONSE_BYTES = 1024 * 1024
|
||||
ACL_BINDINGS = (
|
||||
("/data/aios-gitops/repo.git", "aios-v2r1c4-task-writer"),
|
||||
)
|
||||
CREDENTIAL_SOURCES = (
|
||||
"/etc/credstore.encrypted/aios-v2r1c4-writer-token-v5",
|
||||
"/etc/credstore.encrypted/aios-v2r1c4-publisher-token-v5",
|
||||
"/etc/credstore.encrypted/aios-v2r1c4-metadata-api-bearer-v5",
|
||||
)
|
||||
|
||||
|
||||
class HelperError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Response:
|
||||
status: int
|
||||
body: bytes = b""
|
||||
|
||||
|
||||
def _read_fixed_credential() -> str:
|
||||
flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0)
|
||||
try:
|
||||
fd = os.open(REVOKER_CREDENTIAL_PATH, flags)
|
||||
try:
|
||||
metadata = os.fstat(fd)
|
||||
if (
|
||||
metadata.st_uid != 0
|
||||
or not stat.S_ISREG(metadata.st_mode)
|
||||
or stat.S_IMODE(metadata.st_mode) & 0o077
|
||||
):
|
||||
raise HelperError("REVOCATION_CREDENTIAL_INVALID")
|
||||
payload = os.read(fd, 16385)
|
||||
finally:
|
||||
os.close(fd)
|
||||
except HelperError:
|
||||
raise
|
||||
except OSError:
|
||||
raise HelperError("REVOCATION_CREDENTIAL_INVALID") from None
|
||||
if len(payload) > 16384:
|
||||
raise HelperError("REVOCATION_CREDENTIAL_INVALID")
|
||||
try:
|
||||
token = payload.decode("utf-8").strip()
|
||||
except UnicodeDecodeError:
|
||||
raise HelperError("REVOCATION_CREDENTIAL_INVALID") from None
|
||||
if not token or any(character.isspace() for character in token):
|
||||
raise HelperError("REVOCATION_CREDENTIAL_INVALID")
|
||||
return token
|
||||
|
||||
|
||||
class LiveServices:
|
||||
def http(self, method: str, url: str) -> Response:
|
||||
parsed = urllib.parse.urlsplit(url)
|
||||
if (
|
||||
parsed.scheme != "http"
|
||||
or parsed.hostname != "127.0.0.1"
|
||||
or parsed.port != 3000
|
||||
or parsed.username
|
||||
or parsed.password
|
||||
or parsed.query
|
||||
or parsed.fragment
|
||||
):
|
||||
raise HelperError("REVOCATION_URL_INVALID")
|
||||
token = _read_fixed_credential()
|
||||
request = urllib.request.Request(
|
||||
url,
|
||||
method=method,
|
||||
headers={
|
||||
"Accept": "application/json",
|
||||
"Authorization": f"token {token}",
|
||||
"User-Agent": "aios-v2r1c4-revoker/1",
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.build_opener().open(request, timeout=20) as response:
|
||||
body = response.read(MAX_RESPONSE_BYTES + 1)
|
||||
status_code = response.status
|
||||
except urllib.error.HTTPError as error:
|
||||
body = error.read(MAX_RESPONSE_BYTES + 1)
|
||||
status_code = error.code
|
||||
except Exception:
|
||||
raise HelperError("REVOCATION_HTTP_FAILED") from None
|
||||
if len(body) > MAX_RESPONSE_BYTES:
|
||||
raise HelperError("REVOCATION_HTTP_RESPONSE_TOO_LARGE")
|
||||
return Response(status_code, body)
|
||||
|
||||
def command(self, argv):
|
||||
try:
|
||||
return subprocess.run(
|
||||
list(argv),
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
shell=False,
|
||||
cwd="/",
|
||||
env={"LANG": "C", "LC_ALL": "C", "PATH": "/usr/bin:/usr/sbin"},
|
||||
timeout=20,
|
||||
check=False,
|
||||
)
|
||||
except Exception:
|
||||
raise HelperError("REVOCATION_COMMAND_FAILED") from None
|
||||
|
||||
def path_exists(self, path: str) -> bool:
|
||||
return os.path.lexists(path)
|
||||
|
||||
def remove_path(self, path: str) -> None:
|
||||
try:
|
||||
os.unlink(path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
def _token_list_url(username: str) -> str:
|
||||
return f"{GITEA_API_BASE}/users/{urllib.parse.quote(username, safe='')}/tokens"
|
||||
|
||||
|
||||
def _token_url(username: str, token_name: str) -> str:
|
||||
return f"{_token_list_url(username)}/{urllib.parse.quote(token_name, safe='')}"
|
||||
|
||||
|
||||
def _token_is_present(services, username: str, token_name: str) -> bool:
|
||||
response = services.http("GET", _token_list_url(username))
|
||||
if response.status == 404:
|
||||
return False
|
||||
if response.status != 200:
|
||||
raise HelperError("TOKEN_OBSERVATION_FAILED")
|
||||
try:
|
||||
value = json.loads(response.body.decode("utf-8"))
|
||||
except Exception:
|
||||
raise HelperError("TOKEN_OBSERVATION_FAILED") from None
|
||||
if not isinstance(value, list):
|
||||
raise HelperError("TOKEN_OBSERVATION_FAILED")
|
||||
names = []
|
||||
for item in value:
|
||||
if not isinstance(item, dict) or not isinstance(item.get("name"), str):
|
||||
raise HelperError("TOKEN_OBSERVATION_FAILED")
|
||||
names.append(item["name"])
|
||||
return token_name in names
|
||||
|
||||
|
||||
def _observe_token(services) -> int:
|
||||
present = [
|
||||
_token_is_present(services, username, token_name)
|
||||
for username, token_name in TASK_TOKENS
|
||||
]
|
||||
return 0 if any(present) else 1
|
||||
|
||||
|
||||
def _revoke_token(services) -> int:
|
||||
for username, token_name in TASK_TOKENS:
|
||||
response = services.http("DELETE", _token_url(username, token_name))
|
||||
if response.status not in {204, 404}:
|
||||
raise HelperError("TOKEN_REVOCATION_FAILED")
|
||||
return 0
|
||||
|
||||
|
||||
def _account_url(username: str) -> str:
|
||||
return f"{GITEA_API_BASE}/users/{urllib.parse.quote(username, safe='')}"
|
||||
|
||||
|
||||
def _admin_account_url(username: str) -> str:
|
||||
return f"{GITEA_API_BASE}/admin/users/{urllib.parse.quote(username, safe='')}"
|
||||
|
||||
|
||||
def _observe_account(services, resource: str) -> int:
|
||||
response = services.http("GET", _account_url(TASK_ACCOUNTS[resource]))
|
||||
if response.status == 200:
|
||||
return 0
|
||||
if response.status == 404:
|
||||
return 1
|
||||
raise HelperError("ACCOUNT_OBSERVATION_FAILED")
|
||||
|
||||
|
||||
def _revoke_account(services, resource: str) -> int:
|
||||
response = services.http("DELETE", _admin_account_url(TASK_ACCOUNTS[resource]))
|
||||
if response.status not in {204, 404}:
|
||||
raise HelperError("ACCOUNT_REVOCATION_FAILED")
|
||||
return 0
|
||||
|
||||
|
||||
def _acl_is_present(services, path: str, username: str) -> bool:
|
||||
result = services.command(("/usr/bin/getfacl", "-cp", "--absolute-names", path))
|
||||
if result.returncode != 0:
|
||||
raise HelperError("ACL_OBSERVATION_FAILED")
|
||||
try:
|
||||
lines = result.stdout.decode("utf-8").splitlines()
|
||||
except UnicodeDecodeError:
|
||||
raise HelperError("ACL_OBSERVATION_FAILED") from None
|
||||
prefix = f"user:{username}:"
|
||||
return any(line.startswith(prefix) for line in lines)
|
||||
|
||||
|
||||
def _observe_acl(services) -> int:
|
||||
present = [
|
||||
_acl_is_present(services, path, username)
|
||||
for path, username in ACL_BINDINGS
|
||||
]
|
||||
return 0 if any(present) else 1
|
||||
|
||||
|
||||
def _revoke_acl(services) -> int:
|
||||
for path, username in ACL_BINDINGS:
|
||||
if not _acl_is_present(services, path, username):
|
||||
continue
|
||||
result = services.command(
|
||||
("/usr/bin/setfacl", "-x", f"u:{username}", path)
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise HelperError("ACL_REVOCATION_FAILED")
|
||||
return 0
|
||||
|
||||
|
||||
def _observe_credential_projection(services) -> int:
|
||||
try:
|
||||
present = [services.path_exists(path) for path in CREDENTIAL_SOURCES]
|
||||
except Exception:
|
||||
raise HelperError("CREDENTIAL_SOURCE_OBSERVATION_FAILED") from None
|
||||
if any(type(value) is not bool for value in present):
|
||||
raise HelperError("CREDENTIAL_SOURCE_OBSERVATION_FAILED")
|
||||
return 0 if any(present) else 1
|
||||
|
||||
|
||||
def _revoke_credential_projection(services) -> int:
|
||||
for path in CREDENTIAL_SOURCES:
|
||||
try:
|
||||
if services.path_exists(path):
|
||||
services.remove_path(path)
|
||||
except Exception:
|
||||
raise HelperError("CREDENTIAL_SOURCE_REVOCATION_FAILED") from None
|
||||
return 0
|
||||
|
||||
|
||||
def main(argv=None, *, services=None) -> int:
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument("operation", choices=("observe", "revoke"))
|
||||
parser.add_argument(
|
||||
"resource",
|
||||
choices=("token", "writer", "publisher", "acl", "credential_projection"),
|
||||
)
|
||||
try:
|
||||
arguments = parser.parse_args(argv)
|
||||
active = services if services is not None else LiveServices()
|
||||
if arguments.resource == "token":
|
||||
if arguments.operation == "observe":
|
||||
return _observe_token(active)
|
||||
return _revoke_token(active)
|
||||
if arguments.resource == "acl":
|
||||
if arguments.operation == "observe":
|
||||
return _observe_acl(active)
|
||||
return _revoke_acl(active)
|
||||
if arguments.resource == "credential_projection":
|
||||
if arguments.operation == "observe":
|
||||
return _observe_credential_projection(active)
|
||||
return _revoke_credential_projection(active)
|
||||
if arguments.operation == "observe":
|
||||
return _observe_account(active, arguments.resource)
|
||||
return _revoke_account(active, arguments.resource)
|
||||
except HelperError:
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
[Unit]
|
||||
Description=AIOS exact two-parent governance merge broker (%i)
|
||||
After=network.target aios-gitea.service
|
||||
Requires=aios-gitea.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=root
|
||||
Group=root
|
||||
UMask=0077
|
||||
RuntimeDirectory=aios-exact-two-parent-merge/%i
|
||||
RuntimeDirectoryMode=0700
|
||||
LoadCredentialEncrypted=gitea-token:/run/aios-task-credential-broker/%i-merge.cred
|
||||
LoadCredentialEncrypted=audit-key:/run/aios-task-credential-broker/%i-audit.cred
|
||||
ExecStart=/usr/bin/python3 -m aios_single_owner_governance.exact_two_parent_merge_broker --grant /var/lib/aios-gitops/runtime/exact-two-parent-merge/%i/grant.json --grant-signature /var/lib/aios-gitops/runtime/exact-two-parent-merge/%i/grant.sig --allowed-signers /etc/aios-gitops/exact-two-parent-merge/allowed_signers --token %d/gitea-token --audit-key %d/audit-key --journal /var/lib/aios-gitops/runtime/exact-two-parent-merge/%i/journal/journal.sqlite3 --quarantine-root /run/aios-exact-two-parent-merge/%i
|
||||
Environment=AIOS_GITOPS_WRITES_ENABLED=false
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET
|
||||
ReadOnlyPaths=/var/lib/aios-gitops/runtime/exact-two-parent-merge/%i
|
||||
ReadOnlyPaths=/etc/aios-gitops/exact-two-parent-merge/allowed_signers
|
||||
ReadWritePaths=/var/lib/aios-gitops/runtime/exact-two-parent-merge/%i/journal
|
||||
ReadWritePaths=/data/aios-gitops/repo.git
|
||||
ReadWritePaths=/data/aios-gitops/runtime/gitea-bin/data/repositories/aios/aios.git
|
||||
ReadWritePaths=/run/aios-exact-two-parent-merge/%i
|
||||
IPAddressDeny=any
|
||||
IPAddressAllow=localhost
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
RestrictSUIDSGID=true
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
[Unit]
|
||||
Description=AIOS V2R1C4 task-scoped release V5 Anchor
|
||||
After=local-fs.target
|
||||
Before=aios-v2r1c4-release-writer-v5.service aios-v2r1c4-release-controller-v5.service aios-v2r1c4-release-publisher-v5.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=aios-v2r1c4-task-anchor
|
||||
Group=aios-v2r1c4-release
|
||||
ExecStart=/opt/aios/v2r1c4-release-v5/bin/aios-task-release-anchor --database /var/lib/aios-v2r1c4-release-v5/anchor/release.db --socket /run/aios-v2r1c4-release-v5/anchor.sock --writer-uid=${AIOS_V2R1C4_WRITER_UID} --publisher-uid=${AIOS_V2R1C4_PUBLISHER_UID} --controller-uid=${AIOS_V2R1C4_CONTROLLER_UID}
|
||||
EnvironmentFile=/etc/aios/v2r1c4-release-v5/uids.env
|
||||
RuntimeDirectory=aios-v2r1c4-release-v5/anchor
|
||||
RuntimeDirectoryMode=0750
|
||||
RuntimeDirectoryPreserve=no
|
||||
StateDirectory=aios-v2r1c4-release-v5/anchor
|
||||
StateDirectoryMode=0700
|
||||
ReadWritePaths=/var/lib/aios-v2r1c4-release-v5/anchor /run/aios-v2r1c4-release-v5
|
||||
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectKernelLogs=true
|
||||
ProtectControlGroups=true
|
||||
RestrictSUIDSGID=true
|
||||
RestrictRealtime=true
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
CapabilityBoundingSet=
|
||||
RestrictAddressFamilies=AF_UNIX
|
||||
AmbientCapabilities=
|
||||
SystemCallArchitectures=native
|
||||
UMask=0077
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=2s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
[Unit]
|
||||
Description=AIOS V2R1C4 task-scoped read-only approval observer V5
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=aios-v2r1c4-approval-observer
|
||||
Group=aios-v2r1c4-release
|
||||
SupplementaryGroups=aios-gitops-readers
|
||||
EnvironmentFile=/etc/aios/v2r1c4-release-v5/uids.env
|
||||
LoadCredentialEncrypted=token:/etc/credstore.encrypted/aios-v2r1c4-release-approval-observer-v5.token
|
||||
ExecStart=/opt/aios/v2r1c4-release-v5/bin/aios-task-release-approval-observer --manifest /etc/aios/v2r1c4-release-v5/release-manifest.json --site-config /etc/aios/v2r1c4-release-v5/site-config.json --socket /run/aios-v2r1c4-release-v5/approval-observer/approval-observer.sock --allowed-client-uids=${AIOS_V2R1C4_WRITER_UID},${AIOS_V2R1C4_CONTROLLER_UID},${AIOS_V2R1C4_PUBLISHER_UID}
|
||||
RuntimeDirectory=aios-v2r1c4-release-v5/approval-observer
|
||||
RuntimeDirectoryMode=0750
|
||||
RuntimeDirectoryPreserve=no
|
||||
StateDirectory=aios-v2r1c4-release-v5/approval-observer
|
||||
StateDirectoryMode=0700
|
||||
ReadOnlyPaths=/etc/aios/v2r1c4-release-v5 /data/aios-gitops/repo.git /var/lib/aios-gitops/runtime/phase-a-gates/pr35-ff6743e8516834b19ed15ba36ca6755512fb0e91-20260731T092507Z/phase-a-activation-record.json
|
||||
ReadWritePaths=/run/aios-v2r1c4-release-v5/approval-observer
|
||||
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectKernelLogs=true
|
||||
ProtectControlGroups=true
|
||||
RestrictSUIDSGID=true
|
||||
RestrictRealtime=true
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
CapabilityBoundingSet=
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
||||
AmbientCapabilities=
|
||||
SystemCallArchitectures=native
|
||||
UMask=0077
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=2s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
[Unit]
|
||||
Description=AIOS V2R1C4 task-scoped release V5 controller
|
||||
Requires=aios-v2r1c4-release-anchor-v5.service aios-v2r1c4-release-approval-observer-v5.service
|
||||
After=aios-v2r1c4-release-anchor-v5.service aios-v2r1c4-release-approval-observer-v5.service network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=aios-v2r1c4-task-controller
|
||||
Group=aios-v2r1c4-release
|
||||
EnvironmentFile=/etc/aios/v2r1c4-release-v5/uids.env
|
||||
ExecStart=/opt/aios/v2r1c4-release-v5/bin/aios-task-release-operation --role controller --manifest /etc/aios/v2r1c4-release-v5/release-manifest.json --site-config /etc/aios/v2r1c4-release-v5/site-config.json --socket /run/aios-v2r1c4-release-v5/controller/controller.sock --allowed-client-uids=${AIOS_V2R1C4_CONTROLLER_UID}
|
||||
RuntimeDirectory=aios-v2r1c4-release-v5/controller
|
||||
RuntimeDirectoryMode=0750
|
||||
RuntimeDirectoryPreserve=no
|
||||
StateDirectory=aios-v2r1c4-release-v5/controller
|
||||
StateDirectoryMode=0700
|
||||
ReadOnlyPaths=/etc/aios/v2r1c4-release-v5 /var/cache/aios/releases/V2R1C4
|
||||
ReadWritePaths=/var/lib/aios-v2r1c4-release-v5/controller /run/aios-v2r1c4-release-v5
|
||||
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectKernelLogs=true
|
||||
ProtectControlGroups=true
|
||||
RestrictSUIDSGID=true
|
||||
RestrictRealtime=true
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
CapabilityBoundingSet=
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
||||
IPAddressDeny=any
|
||||
IPAddressAllow=localhost
|
||||
AmbientCapabilities=
|
||||
SystemCallArchitectures=native
|
||||
UMask=0077
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=2s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
[Unit]
|
||||
Description=AIOS V2R1C4 task-scoped release V5 publisher
|
||||
Requires=aios-v2r1c4-release-anchor-v5.service
|
||||
After=aios-v2r1c4-release-anchor-v5.service network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=aios-v2r1c4-task-publisher
|
||||
Group=aios-v2r1c4-release
|
||||
EnvironmentFile=/etc/aios/v2r1c4-release-v5/uids.env
|
||||
ExecStart=/opt/aios/v2r1c4-release-v5/bin/aios-task-release-operation --role publisher --manifest /etc/aios/v2r1c4-release-v5/release-manifest.json --site-config /etc/aios/v2r1c4-release-v5/site-config.json --socket /run/aios-v2r1c4-release-v5/publisher/publisher.sock --allowed-client-uids=${AIOS_V2R1C4_CONTROLLER_UID}
|
||||
RuntimeDirectory=aios-v2r1c4-release-v5/publisher
|
||||
RuntimeDirectoryMode=0750
|
||||
RuntimeDirectoryPreserve=no
|
||||
StateDirectory=aios-v2r1c4-release-v5/publisher
|
||||
StateDirectoryMode=0700
|
||||
ReadOnlyPaths=/etc/aios/v2r1c4-release-v5 /var/cache/aios/releases/V2R1C4
|
||||
ReadWritePaths=/data/aios-gitops/repo.git /var/lib/aios-v2r1c4-release-v5/publisher /run/aios-v2r1c4-release-v5
|
||||
LoadCredentialEncrypted=token:/etc/credstore.encrypted/aios-v2r1c4-publisher-token-v5
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectKernelLogs=true
|
||||
ProtectControlGroups=true
|
||||
RestrictSUIDSGID=true
|
||||
RestrictRealtime=true
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
CapabilityBoundingSet=
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
||||
AmbientCapabilities=
|
||||
SystemCallArchitectures=native
|
||||
UMask=0077
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=2s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
[Unit]
|
||||
Description=AIOS V2R1C4 task-scoped release V5 writer
|
||||
Requires=aios-v2r1c4-release-anchor-v5.service
|
||||
After=aios-v2r1c4-release-anchor-v5.service network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=aios-v2r1c4-task-writer
|
||||
Group=aios-v2r1c4-release
|
||||
EnvironmentFile=/etc/aios/v2r1c4-release-v5/uids.env
|
||||
Environment=AIOS_GITOPS_WRITES_ENABLED=false
|
||||
ExecStart=/opt/aios/v2r1c4-release-v5/bin/aios-task-release-operation --role writer --manifest /etc/aios/v2r1c4-release-v5/release-manifest.json --site-config /etc/aios/v2r1c4-release-v5/site-config.json --socket /run/aios-v2r1c4-release-v5/writer/writer.sock --allowed-client-uids=${AIOS_V2R1C4_CONTROLLER_UID}
|
||||
RuntimeDirectory=aios-v2r1c4-release-v5/writer
|
||||
RuntimeDirectoryMode=0750
|
||||
RuntimeDirectoryPreserve=no
|
||||
StateDirectory=aios-v2r1c4-release-v5/writer
|
||||
StateDirectoryMode=0700
|
||||
ReadOnlyPaths=/etc/aios/v2r1c4-release-v5 /var/cache/aios/releases/V2R1C4 /usr/bin/git /data/aios-gitops/runtime/gitea-bin/data/repositories/aios/aios.git
|
||||
ReadWritePaths=/data/aios-gitops/repo.git /var/lib/aios-v2r1c4-release-v5/quarantine /run/aios-v2r1c4-release-v5/writer
|
||||
LoadCredentialEncrypted=token:/etc/credstore.encrypted/aios-v2r1c4-writer-token-v5
|
||||
LoadCredentialEncrypted=metadata-api-bearer:/etc/credstore.encrypted/aios-v2r1c4-metadata-api-bearer-v5
|
||||
LoadCredentialEncrypted=gitea-publish-token:/etc/credstore.encrypted/aios-v2r1c4-writer-token-v5
|
||||
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ProtectClock=true
|
||||
ProtectHostname=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectKernelLogs=true
|
||||
ProtectControlGroups=true
|
||||
ProtectProc=invisible
|
||||
ProcSubset=pid
|
||||
RestrictSUIDSGID=true
|
||||
RestrictRealtime=true
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
CapabilityBoundingSet=
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET
|
||||
AmbientCapabilities=
|
||||
SystemCallArchitectures=native
|
||||
UMask=0077
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=2s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
[Unit]
|
||||
Description=AIOS V2R1C4 fixed root action broker
|
||||
After=local-fs.target
|
||||
Before=aios-v2r1c4-release-controller-v5.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
Group=root
|
||||
UMask=0077
|
||||
Environment=AIOS_GITOPS_WRITES_ENABLED=false
|
||||
LoadCredentialEncrypted=gitea-api-token:/etc/credstore.encrypted/aios-v2r1c4-root-action-broker-gitea-api-token
|
||||
LoadCredentialEncrypted=owner-id:/etc/credstore.encrypted/aios-v2r1c4-root-action-broker-owner-id
|
||||
LoadCredentialEncrypted=metadata-writer-token:/etc/credstore.encrypted/aios-v2r1c4-root-action-broker-metadata-writer-token
|
||||
LoadCredentialEncrypted=revoker-token:/etc/credstore.encrypted/aios-v2r1c4-root-action-broker-revoker-token
|
||||
LoadCredentialEncrypted=old-token-0:/etc/credstore.encrypted/aios-v2r1c4-writer-token-v5
|
||||
LoadCredentialEncrypted=old-token-1:/etc/credstore.encrypted/aios-v2r1c4-publisher-token-v5
|
||||
# Installed source identity: aios_task_scoped_release.action_broker
|
||||
ExecStart=/usr/bin/python3.12 /opt/aios/v2r1c4-release-v5/libexec/action-broker.pyz --config /etc/aios/v2r1c4-release-v5/root-action-config.json --manifest /etc/aios/v2r1c4-release-v5/release-manifest.json --python /usr/bin/python3.12 --program /opt/aios/v2r1c4-release-v5/libexec/action-program.pyz --socket /run/aios-v2r1c4-release-v5/root-action-broker.sock
|
||||
Restart=on-failure
|
||||
RestartSec=2s
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
# The approved core-metadata plan must observe the host /tmp scan root. The
|
||||
# path is mounted read-only below, so no host temporary-file write is exposed.
|
||||
PrivateTmp=false
|
||||
PrivateDevices=true
|
||||
ProtectClock=true
|
||||
ProtectHostname=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectKernelLogs=true
|
||||
ProtectControlGroups=true
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_NETLINK
|
||||
IPAddressDeny=any
|
||||
IPAddressAllow=localhost
|
||||
RestrictNamespaces=true
|
||||
RestrictRealtime=true
|
||||
RestrictSUIDSGID=true
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
SystemCallArchitectures=native
|
||||
RemoveIPC=true
|
||||
CapabilityBoundingSet=CAP_CHOWN CAP_DAC_OVERRIDE CAP_FOWNER CAP_KILL CAP_SETGID CAP_SETUID
|
||||
AmbientCapabilities=
|
||||
ReadOnlyPaths=/etc/aios/v2r1c4-release-v5/root-action-config.json /etc/aios/v2r1c4-release-v5/release-manifest.json /usr/bin/python3.12 /usr/bin/git /opt/aios/v2r1c4-release-v5/libexec/action-broker.pyz /opt/aios/v2r1c4-release-v5/libexec/action-program.pyz /run/credentials/aios-v2r1c4-root-action-broker.service/gitea-api-token /data/aios-gitops/runtime/gitea-bin/data/repositories/aios/aios.git /var/cache/aios/releases/V2R1C4/main-reconciliation-source-approval.json
|
||||
ReadOnlyPaths=/run/credentials/aios-v2r1c4-root-action-broker.service/owner-id /run/credentials/aios-v2r1c4-root-action-broker.service/metadata-writer-token /run/credentials/aios-v2r1c4-root-action-broker.service/revoker-token /run/credentials/aios-v2r1c4-root-action-broker.service/old-token-0 /run/credentials/aios-v2r1c4-root-action-broker.service/old-token-1
|
||||
ReadOnlyPaths=-/home/aiman/.cache/AIOS-V2R1C4-1857b886.tar.gz -/home/aiman/.cache/AIOS-V2R1C3-ROLLBACK-7db5e458.tar.gz -/var/cache/aios/releases/V2R1C4 -/var/cache/aios/releases/V2R1C4/artifact-file-manifest.json -/var/cache/aios/releases/V2R1C4/bindings/source-commit.oid -/var/cache/aios/releases/V2R1C4/bindings/source-tree.oid -/opt/aios/releases/V2R1C3 -/opt/aios/releases/V2R1C4/deploy/aios-v2/systemd/openclaw-gateway-v2r1c4.service -/usr/lib/node_modules/openclaw -/var/lib/aios/openclaw-session -/var/lib/aios/openclaw-provenance -/var/lib/aios/openclaw-runtime -/var/lib/aios/release-evidence -/etc/systemd/system/aios-v2r1c3-production.service -/etc/systemd/system/aios-v2r1c2-portal.service /proc/sys/kernel/core_pattern -/var/lib/systemd/coredump -/var/crash /tmp /var/tmp /usr/bin/python3 /usr/bin/tar /usr/bin/install /usr/bin/systemctl /usr/bin/curl /usr/bin/ss /usr/bin/coredumpctl /usr/bin/journalctl /usr/bin/chown /usr/bin/find /usr/sbin/runuser /usr/bin/test /usr/bin/rm /opt/aios/v2r1c4-release-v5/bin/aios-task-release-revoker /usr/bin/getfacl /usr/bin/setfacl
|
||||
BindReadOnlyPaths=-/home/aiman/.cache/AIOS-V2R1C4-1857b886.tar.gz -/home/aiman/.cache/AIOS-V2R1C3-ROLLBACK-7db5e458.tar.gz
|
||||
ReadWritePaths=/data/aios-gitops/repo.git /run/aios-v2r1c4-release-v5
|
||||
ReadWritePaths=/etc/credstore.encrypted/aios-v2r1c4-writer-token-v5 /etc/credstore.encrypted/aios-v2r1c4-publisher-token-v5 /etc/credstore.encrypted/aios-v2r1c4-metadata-api-bearer-v5
|
||||
ReadWritePaths=-/opt/aios/releases/V2R1C4 -/opt/aios/releases/.V2R1C4-stage -/etc/systemd/system/aios-v2r1c4-candidate.service -/etc/systemd/system/.aios-v2r1c4-candidate.service.tmp -/etc/systemd/system/aios-v2r1c4-production.service -/etc/systemd/system/.aios-v2r1c4-production.service.tmp -/etc/aios-v2/aios-v2r1c4-candidate.env -/etc/aios-v2/.aios-v2r1c4-candidate.env.tmp -/etc/aios-v2/aios-v2r1c4-production.env -/etc/aios-v2/.aios-v2r1c4-production.env.tmp -/var/lib/aios/openclaw-session-v2r1c4 -/var/lib/aios/openclaw-session-v2r1c4.migration.json -/var/lib/aios/.openclaw-session-v2r1c4.migration.json.tmp
|
||||
ReadWritePaths=-/var/lib/aios/openclaw-session-v2r1c4/home -/var/lib/aios/openclaw-session-v2r1c4/tmp -/var/lib/aios/openclaw-provenance-v2r1c4 -/var/lib/aios/release-evidence
|
||||
ReadWritePaths=-/etc/systemd/system/openclaw-gateway-v2r1c4.service -/etc/systemd/system/.openclaw-gateway-v2r1c4.service.tmp -/etc/aios/openclaw-gateway-v2r1c4.env -/etc/aios/.openclaw-gateway-v2r1c4.env.tmp
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
d /run/aios-v2r1c4-release-v5 0750 aios-v2r1c4-task-anchor aios-v2r1c4-release -
|
||||
d /var/lib/aios-v2r1c4-release-v5 0750 root aios-v2r1c4-release -
|
||||
d /var/lib/aios-v2r1c4-release-v5/anchor 0700 aios-v2r1c4-task-anchor aios-v2r1c4-task-anchor -
|
||||
d /var/lib/aios-v2r1c4-release-v5/quarantine 0700 aios-v2r1c4-task-writer aios-v2r1c4-task-writer -
|
||||
d /var/lib/aios-v2r1c4-release-v5/controller 0700 aios-v2r1c4-task-controller aios-v2r1c4-task-controller -
|
||||
d /var/lib/aios-v2r1c4-release-v5/publisher 0700 aios-v2r1c4-task-publisher aios-v2r1c4-task-publisher -
|
||||
d /etc/aios/v2r1c4-release-v5 0750 root aios-v2r1c4-release -
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+1115
File diff suppressed because it is too large
Load Diff
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"action": "record_main_reconciliation",
|
||||
"action_plan_sha256": "00c0a3d9fb056de6804b471c24e205f7580658411df754f0e359b96348221d14",
|
||||
"idempotency_key": "v2r1c4-main-reconciliation-1",
|
||||
"payload": {
|
||||
"authority_main_after_oid": "8291b8368bf690416a3159ac5d21d9f8139c2e61",
|
||||
"authority_main_before_oid": "875d41c3101093149414292067fd700dc214d4d3",
|
||||
"evidence_sha256": "6666666666666666666666666666666666666666666666666666666666666666",
|
||||
"fast_forward_verified": true,
|
||||
"gitea_api_main_oid": "8291b8368bf690416a3159ac5d21d9f8139c2e61",
|
||||
"gitea_main_after_oid": "8291b8368bf690416a3159ac5d21d9f8139c2e61",
|
||||
"gitea_main_oid": "8291b8368bf690416a3159ac5d21d9f8139c2e61",
|
||||
"source_approval_verified": true,
|
||||
"status": "PASS"
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"action": "import_exact_objects",
|
||||
"action_plan_sha256": "8d78db4630cab1b2513cb5635dfb219d388eaad3c97e6db7450263a3b8ea80e5",
|
||||
"idempotency_key": "v2r1c4-import-objects-1",
|
||||
"payload": {
|
||||
"evidence_closure_oid": "1111111111111111111111111111111111111111",
|
||||
"git_bundle_sha256": "8888888888888888888888888888888888888888888888888888888888888888",
|
||||
"source_commit_oid": "1857b88690cd1c0119df984534c260b6f217a21a"
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"action": "publish_exact_branch",
|
||||
"action_plan_sha256": "45dcef857c43c6ccf6564971cc2f0bfefa2575d62477a14721ad333a3b4885e1",
|
||||
"idempotency_key": "v2r1c4-publish-branch-1",
|
||||
"payload": {
|
||||
"target_oid": "1111111111111111111111111111111111111111",
|
||||
"target_ref": "refs/heads/patch/codex/AIOS-V2R1C4-PROTECTED-DISPATCH-PRIVACY-REFREEZE"
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"action": "deploy_candidate",
|
||||
"action_plan_sha256": "55f2f8e12d83f61d47cdc2bc33c6cf6a13853dff466dc9a12e0950706085e43b",
|
||||
"idempotency_key": "v2r1c4-deploy-candidate-1",
|
||||
"payload": {
|
||||
"artifact_sha256": "39523caa48f39b33e5e5df785bb568e0381d8b20301746c280442f73f294f2b2",
|
||||
"release_root_sha256": "6c32487388b818763a75f2267d9bd9ff32a0dc11297546005281924a073e981c",
|
||||
"rollback_artifact_sha256": "dd0dbbe21db14722996636347ea1d22917436c688d5f0b7055b5bfb1776c4d95"
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"action": "migrate_owner_identity",
|
||||
"action_plan_sha256": "72f28d5beb8f89a4121864ec5fb91ddb73baf841c3bdfd777b5123221d6e6470",
|
||||
"idempotency_key": "v2r1c4-migrate-owner-1",
|
||||
"payload": {
|
||||
"mode": "quiesced-transactional"
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"action": "scan_core_metadata",
|
||||
"action_plan_sha256": "0d1b976d974f3fcd551b739cd261efda780fbc3a456fd56a766b861c4316a360",
|
||||
"idempotency_key": "v2r1c4-core-scan-1",
|
||||
"payload": {
|
||||
"equivalent_scan_required": true
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"action": "promote_production",
|
||||
"action_plan_sha256": "44c3305eb7fa60a1fc0301de2f6ed5e0d294295d66cc13e2ab4ecb9c65aa5a3b",
|
||||
"idempotency_key": "v2r1c4-promote-production-1",
|
||||
"payload": {
|
||||
"artifact_sha256": "39523caa48f39b33e5e5df785bb568e0381d8b20301746c280442f73f294f2b2",
|
||||
"release_root_sha256": "6c32487388b818763a75f2267d9bd9ff32a0dc11297546005281924a073e981c",
|
||||
"rollback_artifact_sha256": "dd0dbbe21db14722996636347ea1d22917436c688d5f0b7055b5bfb1776c4d95"
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"action": "record_fresh_acceptance",
|
||||
"action_plan_sha256": "ec0fae7b94c9c029023d338705ab0162a5464d17cb2b7cc91831b40e47c52160",
|
||||
"idempotency_key": "v2r1c4-fresh-owner-event-1",
|
||||
"payload": {
|
||||
"accepted": true,
|
||||
"canonical_request_sha256": "1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"canonical_request_sha256_recomputed": true,
|
||||
"ed25519_signature_verified": true,
|
||||
"effective_provider": "chatgpt-supervisor",
|
||||
"event_id_hash": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
||||
"event_replay_checked": true,
|
||||
"event_replayed": false,
|
||||
"event_source": "WECHAT_NATURAL_INBOUND",
|
||||
"evidence_sha256": "5555555555555555555555555555555555555555555555555555555555555555",
|
||||
"external_executor_contacted": false,
|
||||
"inbound_pass": true,
|
||||
"natural_inbound": true,
|
||||
"replay_nonce_sha256": "6666666666666666666666666666666666666666666666666666666666666666",
|
||||
"deployment_observed_at": "2026-07-31T02:30:00Z",
|
||||
"inbound_received_at": "2026-07-31T02:31:00Z",
|
||||
"protected_dispatch_started_at": "2026-07-31T02:32:00Z",
|
||||
"reply_delivered_at": "2026-07-31T02:33:00Z",
|
||||
"observed_at": "2026-07-31T02:34:00Z",
|
||||
"deployment_target_oid": "1111111111111111111111111111111111111111",
|
||||
"runtime_layout_sha256": "7777777777777777777777777777777777777777777777777777777777777777",
|
||||
"production_unit_sha256": "8888888888888888888888888888888888888888888888888888888888888888",
|
||||
"production_service": "aios-v2r1c4-production.service",
|
||||
"owner_principal": "principal:sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"provider_receipt": {
|
||||
"event_id_hash": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
||||
"nonce_sha256": "6666666666666666666666666666666666666666666666666666666666666666",
|
||||
"owner_principal": "principal:sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"deployment_target_oid": "1111111111111111111111111111111111111111",
|
||||
"runtime_layout_sha256": "7777777777777777777777777777777777777777777777777777777777777777",
|
||||
"production_unit_sha256": "8888888888888888888888888888888888888888888888888888888888888888",
|
||||
"inbound_received_at": "2026-07-31T02:31:00Z",
|
||||
"protected_dispatch_started_at": "2026-07-31T02:32:00Z",
|
||||
"reply_delivered_at": "2026-07-31T02:33:00Z",
|
||||
"delivery_status": "DELIVERED",
|
||||
"dispatch_mode": "PROTECTED_DISPATCH"
|
||||
},
|
||||
"previous_block_manifest_sha256": "8ad6ec28c69f25c63d0ac7dff95f574b39119be12b00184408242460df33442c",
|
||||
"previous_block_record_sha256": "91c5892f30cefe0db9448abef799c6e97237b92e60d0704ba4af408ee33acf68",
|
||||
"protected_dispatch_invoked": true,
|
||||
"provider_binding_enforced": true,
|
||||
"provider_binding_result": "ALREADY_COMPLIANT",
|
||||
"provider_hint_overridden": false,
|
||||
"raw_owner_id_occurrences": 0,
|
||||
"real_user_impersonated": false,
|
||||
"receipt_cross_reference_verified": true,
|
||||
"receipt_sha256": "4444444444444444444444444444444444444444444444444444444444444444",
|
||||
"reply_delivered": true,
|
||||
"required_provider": "chatgpt-supervisor",
|
||||
"signature_payload_sha256": "2222222222222222222222222222222222222222222222222222222222222222",
|
||||
"signature_payload_sha256_recomputed": true,
|
||||
"signature_sha256": "3333333333333333333333333333333333333333333333333333333333333333",
|
||||
"simulation": true,
|
||||
"status": "PASS",
|
||||
"unauthorized_write_occurred": false
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"action": "publish_exact_tag",
|
||||
"action_plan_sha256": "f2454de5fc93ad2a8617a85f46713e36a3bcae3806339601dd67d7fafe72fc30",
|
||||
"idempotency_key": "v2r1c4-publish-tag-1",
|
||||
"payload": {
|
||||
"unlock_receipt": {
|
||||
"candidate_deployment_evidence_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"core_metadata_scan_evidence_sha256": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
|
||||
"fresh_owner_acceptance_evidence_sha256": "5555555555555555555555555555555555555555555555555555555555555555",
|
||||
"identity_migration_evidence_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
"main_reconciliation_evidence_sha256": "6666666666666666666666666666666666666666666666666666666666666666",
|
||||
"manifest_sha256": "d07dff30311ca46ffe2c888c705810ecccc22cc9d82e0764a9b5b973a2e78ae7",
|
||||
"production_promotion_evidence_sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
|
||||
"publisher_unlocked": true,
|
||||
"schema_id": "AIOS_RELEASE_PUBLISHER_UNLOCK",
|
||||
"schema_version": 1,
|
||||
"unlock_receipt_sha256": "f16ee944c1bdf746f4d1cccc9cc5965297f3852b77efd018809b53c7333422dc"
|
||||
}
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"action": "write_release_registry",
|
||||
"action_plan_sha256": "f7e1010008689e5b8b9ac21f7cbe9cee1bc0c06bf46fe5fbf8b338e9a34fd53f",
|
||||
"idempotency_key": "v2r1c4-registry-c3-c4-1",
|
||||
"payload": {
|
||||
"execute": true,
|
||||
"preconditions": {
|
||||
"create_only": true,
|
||||
"deployment_evidence_verified": true,
|
||||
"fresh_acceptance_verified": true,
|
||||
"governed_commit_file_only": true,
|
||||
"publisher_unlocked": true,
|
||||
"read_single_commit_wait_reread_verified": true,
|
||||
"tag_verified": true,
|
||||
"template_compatibility_verified": true,
|
||||
"two_stage_transaction_verified": true
|
||||
},
|
||||
"transitions": [
|
||||
{
|
||||
"deployment_evidence_sha256": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
||||
"from_version": "V2R1C2",
|
||||
"registry_record": {
|
||||
"deployment_evidence_sha256": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
||||
"fresh_acceptance_evidence_sha256": "4444444444444444444444444444444444444444444444444444444444444444",
|
||||
"status": "PRODUCTION_DEPLOYED",
|
||||
"tag_oid": "a159d0ec4ea558137c1567f558ef41bfd9f5122d",
|
||||
"target_oid": "7db5e458b225b6eabe1bb59fc119602abde8a629",
|
||||
"version": "V2R1C3"
|
||||
},
|
||||
"release_artifact_sha256": "8888888888888888888888888888888888888888888888888888888888888888",
|
||||
"rollback_artifact_sha256": "9999999999999999999999999999999999999999999999999999999999999999",
|
||||
"tag_oid": "a159d0ec4ea558137c1567f558ef41bfd9f5122d",
|
||||
"target_oid": "7db5e458b225b6eabe1bb59fc119602abde8a629",
|
||||
"to_version": "V2R1C3"
|
||||
},
|
||||
{
|
||||
"deployment_evidence_sha256": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
|
||||
"fresh_acceptance_evidence_sha256": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
"from_version": "V2R1C3",
|
||||
"registry_record": {
|
||||
"deployment_evidence_sha256": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
|
||||
"fresh_acceptance_evidence_sha256": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
"status": "PRODUCTION_DEPLOYED",
|
||||
"tag_oid": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
"target_oid": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"version": "V2R1C4"
|
||||
},
|
||||
"release_artifact_sha256": "39523caa48f39b33e5e5df785bb568e0381d8b20301746c280442f73f294f2b2",
|
||||
"rollback_artifact_sha256": "dd0dbbe21db14722996636347ea1d22917436c688d5f0b7055b5bfb1776c4d95",
|
||||
"tag_oid": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
"target_oid": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"to_version": "V2R1C4"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"action": "write_production_baseline",
|
||||
"action_plan_sha256": "06bd3401b8d8fcf0de8a3b276ae7e82f1873ec209d8c0c8c62af8bf1245c3198",
|
||||
"idempotency_key": "v2r1c4-baseline-c2-c3-c4-1",
|
||||
"payload": {
|
||||
"execute": true,
|
||||
"preconditions": {
|
||||
"baseline_compatibility_verified": true,
|
||||
"evidence_binding_verified": true,
|
||||
"fresh_acceptance_verified": true,
|
||||
"governed_commit_file_only": true,
|
||||
"publisher_unlocked": true,
|
||||
"read_single_commit_wait_reread_verified": true,
|
||||
"rollback_binding_verified": true,
|
||||
"service_binding_verified": true,
|
||||
"tag_verified": true,
|
||||
"two_stage_transaction_verified": true
|
||||
},
|
||||
"transitions": [
|
||||
{
|
||||
"baseline_record": {
|
||||
"deployment_evidence_sha256": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
||||
"fresh_acceptance_evidence_sha256": "4444444444444444444444444444444444444444444444444444444444444444",
|
||||
"production_service": "aios-v2r1c3-production.service",
|
||||
"release_artifact_sha256": "8888888888888888888888888888888888888888888888888888888888888888",
|
||||
"rollback_artifact_sha256": "9999999999999999999999999999999999999999999999999999999999999999",
|
||||
"status": "PRODUCTION_DEPLOYED",
|
||||
"tag_oid": "a159d0ec4ea558137c1567f558ef41bfd9f5122d",
|
||||
"target_oid": "7db5e458b225b6eabe1bb59fc119602abde8a629",
|
||||
"version": "V2R1C3"
|
||||
},
|
||||
"deployment_evidence_sha256": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
||||
"from_version": "V2R1C2",
|
||||
"release_artifact_sha256": "8888888888888888888888888888888888888888888888888888888888888888",
|
||||
"rollback_artifact_sha256": "9999999999999999999999999999999999999999999999999999999999999999",
|
||||
"tag_oid": "a159d0ec4ea558137c1567f558ef41bfd9f5122d",
|
||||
"target_oid": "7db5e458b225b6eabe1bb59fc119602abde8a629",
|
||||
"to_version": "V2R1C3"
|
||||
},
|
||||
{
|
||||
"baseline_record": {
|
||||
"deployment_evidence_sha256": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
|
||||
"fresh_acceptance_evidence_sha256": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
"production_service": "aios-v2r1c4-production.service",
|
||||
"release_artifact_sha256": "39523caa48f39b33e5e5df785bb568e0381d8b20301746c280442f73f294f2b2",
|
||||
"rollback_artifact_sha256": "dd0dbbe21db14722996636347ea1d22917436c688d5f0b7055b5bfb1776c4d95",
|
||||
"status": "PRODUCTION_DEPLOYED",
|
||||
"tag_oid": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
"target_oid": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"version": "V2R1C4"
|
||||
},
|
||||
"deployment_evidence_sha256": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
|
||||
"fresh_acceptance_evidence_sha256": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
"from_version": "V2R1C3",
|
||||
"release_artifact_sha256": "39523caa48f39b33e5e5df785bb568e0381d8b20301746c280442f73f294f2b2",
|
||||
"rollback_artifact_sha256": "dd0dbbe21db14722996636347ea1d22917436c688d5f0b7055b5bfb1776c4d95",
|
||||
"tag_oid": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
"target_oid": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"to_version": "V2R1C4"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"action": "revoke_task_credentials",
|
||||
"action_plan_sha256": "280d2fb143079396351eddb5d907fef4d8150f27c12cfca23a8833ee59492076",
|
||||
"idempotency_key": "v2r1c4-revoke-1",
|
||||
"payload": {
|
||||
"auto_revoke": true
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"action": "complete",
|
||||
"action_plan_sha256": "d08c2ed77e8595c6c52d6c9e055b728e757027088b26fce98f1ef1f6f1d9bd77",
|
||||
"idempotency_key": "v2r1c4-complete-1",
|
||||
"payload": {
|
||||
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user