Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5528868ef9 |
+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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
+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())
|
||||
@@ -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"}}}
|
||||
@@ -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,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": {
|
||||
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"evidence_sha256": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
|
||||
"paths_scanned_sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
|
||||
"raw_owner_id_occurrences": 0,
|
||||
"scan_status": "COMPLETE_NO_CORE_POSSIBLE",
|
||||
"status": "PASS"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"artifact_sha256": "39523caa48f39b33e5e5df785bb568e0381d8b20301746c280442f73f294f2b2",
|
||||
"evidence_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"candidate_active": true,
|
||||
"candidate_service": "aios-v2r1c4-candidate.service",
|
||||
"production_unchanged": true,
|
||||
"production_unit_drop_in_paths": [],
|
||||
"production_unit_fragment_path": "/etc/systemd/system/aios-v2r1c4-production.service",
|
||||
"production_unit_mutable_path_conflicts": [],
|
||||
"production_unit_no_new_privileges": true,
|
||||
"production_unit_protect_system": "strict",
|
||||
"production_unit_read_only_paths": [
|
||||
"/usr/lib/node_modules/openclaw"
|
||||
],
|
||||
"production_unit_sha256": "a733996ca18c2717572a20f99e3751687530ffb23dbda7e24adf606234a5d833",
|
||||
"release_root_sha256": "af194974826f311110e6d545c04148cfe570414c2b87f0c3d04f910d4a8801d0",
|
||||
"rollback_artifact_sha256": "dd0dbbe21db14722996636347ea1d22917436c688d5f0b7055b5bfb1776c4d95",
|
||||
"rollback_verified": true,
|
||||
"runtime_layout_sha256": "1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"status": "PASS",
|
||||
"target_oid": "55fb0eea4abf888e71f02567576c657c516b91a4"
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"core_metadata_evidence_sha256": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
"incident_block_manifest_sha256": "8ad6ec28c69f25c63d0ac7dff95f574b39119be12b00184408242460df33442c",
|
||||
"incident_block_record_sha256": "91c5892f30cefe0db9448abef799c6e97237b92e60d0704ba4af408ee33acf68",
|
||||
"provider_contract_evidence_sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
|
||||
"pseudonymization_evidence_sha256": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
||||
"release_artifact_sha256": "39523caa48f39b33e5e5df785bb568e0381d8b20301746c280442f73f294f2b2",
|
||||
"release_root_input_sha256": "64db96d4811e890e329430f3428bd53df965b19de714f799acd5ea8466329b98",
|
||||
"release_root_sha256": "f7d1285670928b1927b8b74ff5bc126ceb72c2b5858c1e259ecbdfb177d469a6",
|
||||
"release_version": "V2R1C4",
|
||||
"repository_identity": "aios/aios",
|
||||
"rollback_artifact_sha256": "dd0dbbe21db14722996636347ea1d22917436c688d5f0b7055b5bfb1776c4d95",
|
||||
"schema_id": "AIOS_EVIDENCE_CLOSURE_INPUT",
|
||||
"schema_version": 1,
|
||||
"security_review_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
"signature_witness_evidence_sha256": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
|
||||
"source_commit_oid": "1857b88690cd1c0119df984534c260b6f217a21a",
|
||||
"source_date_epoch": 1785456000,
|
||||
"source_tree_oid": "17c153de2338f570f70cb7407e7dda3c596b524b",
|
||||
"test_summary_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"algorithm_id":"AIOS_EVIDENCE_CLOSURE_V1","artifacts":{"release_sha256":"39523caa48f39b33e5e5df785bb568e0381d8b20301746c280442f73f294f2b2","rollback_sha256":"dd0dbbe21db14722996636347ea1d22917436c688d5f0b7055b5bfb1776c4d95"},"canonicalization":"AIOS_CANONICAL_JSON_V1","evidence":{"core_metadata_sha256":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","provider_contract_sha256":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","pseudonymization_sha256":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","security_review_sha256":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","signature_witness_sha256":"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd","test_summary_sha256":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},"incident":{"blocked_manifest_sha256":"8ad6ec28c69f25c63d0ac7dff95f574b39119be12b00184408242460df33442c","blocked_record_sha256":"91c5892f30cefe0db9448abef799c6e97237b92e60d0704ba4af408ee33acf68","immutable":true},"input_sha256":"789933e273445b676affb5c7a31b63583c772fd60d3bbf15a9bdf0261b55cc73","release_root":{"descriptor_sha256":"f7d1285670928b1927b8b74ff5bc126ceb72c2b5858c1e259ecbdfb177d469a6","input_sha256":"64db96d4811e890e329430f3428bd53df965b19de714f799acd5ea8466329b98"},"release_version":"V2R1C4","repository_identity":"aios/aios","schema_id":"AIOS_EVIDENCE_CLOSURE_MANIFEST","schema_version":1,"source":{"commit_oid":"1857b88690cd1c0119df984534c260b6f217a21a","tree_oid":"17c153de2338f570f70cb7407e7dda3c596b524b"},"source_date_epoch":1785456000}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"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 @@
|
||||
{
|
||||
"database_integrity": "PASS",
|
||||
"event_relationships_preserved": true,
|
||||
"evidence_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
"immutable_block_record_preserved": true,
|
||||
"pseudonym_consistency": "PASS",
|
||||
"raw_owner_id_occurrences_after": 0,
|
||||
"raw_owner_id_occurrences_before": 233,
|
||||
"status": "PASS"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"artifact_sha256": "39523caa48f39b33e5e5df785bb568e0381d8b20301746c280442f73f294f2b2",
|
||||
"candidate_stopped": true,
|
||||
"evidence_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"production_active": true,
|
||||
"production_promotion_receipt_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
"production_service": "aios-v2r1c4-production.service",
|
||||
"production_unit_sha256": "a733996ca18c2717572a20f99e3751687530ffb23dbda7e24adf606234a5d833",
|
||||
"release_root_sha256": "af194974826f311110e6d545c04148cfe570414c2b87f0c3d04f910d4a8801d0",
|
||||
"rollback_artifact_sha256": "dd0dbbe21db14722996636347ea1d22917436c688d5f0b7055b5bfb1776c4d95",
|
||||
"rollback_verified": true,
|
||||
"runtime_layout_sha256": "1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"status": "PASS",
|
||||
"target_oid": "55fb0eea4abf888e71f02567576c657c516b91a4"
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
+1048
File diff suppressed because it is too large
Load Diff
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"artifact_file_manifest_sha256": "3333333333333333333333333333333333333333333333333333333333333333",
|
||||
"candidate_ref": "refs/heads/patch/codex/AIOS-V2R1C4-PROTECTED-DISPATCH-PRIVACY-REFREEZE",
|
||||
"deployment_plan_sha256": "4444444444444444444444444444444444444444444444444444444444444444",
|
||||
"previous_baseline": {
|
||||
"tag_oid": "a159d0ec4ea558137c1567f558ef41bfd9f5122d",
|
||||
"target_oid": "7db5e458b225b6eabe1bb59fc119602abde8a629",
|
||||
"version": "V2R1C3"
|
||||
},
|
||||
"release_artifact_sha256": "39523caa48f39b33e5e5df785bb568e0381d8b20301746c280442f73f294f2b2",
|
||||
"release_version": "V2R1C4",
|
||||
"repository_identity": "aios/aios",
|
||||
"rollback_artifact_sha256": "dd0dbbe21db14722996636347ea1d22917436c688d5f0b7055b5bfb1776c4d95",
|
||||
"rollback_plan_sha256": "5555555555555555555555555555555555555555555555555555555555555555",
|
||||
"runtime_layout_sha256": "a426ac32d35c1154e2b47870463142c1084991e2d4de4e72b99ff93327f1f0b8",
|
||||
"schema_id": "AIOS_RELEASE_ROOT_INPUT",
|
||||
"schema_version": 1,
|
||||
"source_commit_oid": "1857b88690cd1c0119df984534c260b6f217a21a",
|
||||
"source_date_epoch": 1785456000,
|
||||
"source_tree_oid": "17c153de2338f570f70cb7407e7dda3c596b524b",
|
||||
"target_environment": "production"
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"algorithm_id":"AIOS_RELEASE_ROOT_V1","artifacts":{"file_manifest_sha256":"3333333333333333333333333333333333333333333333333333333333333333","release_sha256":"39523caa48f39b33e5e5df785bb568e0381d8b20301746c280442f73f294f2b2","rollback_sha256":"dd0dbbe21db14722996636347ea1d22917436c688d5f0b7055b5bfb1776c4d95"},"candidate_ref":"refs/heads/patch/codex/AIOS-V2R1C4-PROTECTED-DISPATCH-PRIVACY-REFREEZE","canonicalization":"AIOS_CANONICAL_JSON_V1","input_sha256":"64db96d4811e890e329430f3428bd53df965b19de714f799acd5ea8466329b98","plans":{"deployment_sha256":"4444444444444444444444444444444444444444444444444444444444444444","rollback_sha256":"5555555555555555555555555555555555555555555555555555555555555555","runtime_layout_sha256":"a426ac32d35c1154e2b47870463142c1084991e2d4de4e72b99ff93327f1f0b8"},"previous_baseline":{"tag_oid":"a159d0ec4ea558137c1567f558ef41bfd9f5122d","target_oid":"7db5e458b225b6eabe1bb59fc119602abde8a629","version":"V2R1C3"},"release_version":"V2R1C4","repository_identity":"aios/aios","schema_id":"AIOS_RELEASE_ROOT_DESCRIPTOR","schema_version":1,"source":{"commit_oid":"1857b88690cd1c0119df984534c260b6f217a21a","tree_oid":"17c153de2338f570f70cb7407e7dda3c596b524b"},"source_date_epoch":1785456000,"target_environment":"production"}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
[Unit]
|
||||
Description=AIOS V2R1C4 Production Runtime Example
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=aios
|
||||
Group=aios
|
||||
ExecStart=/opt/aios/v2r1c4/bin/start
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ReadOnlyPaths=/usr/lib/node_modules/openclaw
|
||||
ReadWritePaths=/var/lib/aios/v2r1c4 /run/aios-v2r1c4
|
||||
RuntimeDirectory=aios-v2r1c4
|
||||
StateDirectory=aios/v2r1c4
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"absolute_symlink_policy":"DECLARED_ONLY","algorithm_id":"AIOS_RUNTIME_LAYOUT_V1","artifact_file_manifest_sha256":"3333333333333333333333333333333333333333333333333333333333333333","artifact_sha256":"39523caa48f39b33e5e5df785bb568e0381d8b20301746c280442f73f294f2b2","canonicalization":"AIOS_CANONICAL_JSON_V1","external_dependencies":[{"artifact_path":"vendor/openclaw-weixin/node_modules/openclaw","link_target":"/usr/lib/node_modules/openclaw","package_json_relative_path":"package.json","package_json_sha256":"0000000000000000000000000000000000000000000000000000000000000000","package_name":"openclaw","package_version":"2026.6.11","required_read_only_path":"/usr/lib/node_modules/openclaw","resolution_policy":"EXTERNAL_READ_ONLY_PINNED","target_tree_sha256":"1111111111111111111111111111111111111111111111111111111111111111","target_type":"directory"}],"production_unit":{"name":"aios-v2r1c4-production.service","no_new_privileges":true,"protect_system":"strict","required_read_only_paths":["/usr/lib/node_modules/openclaw"],"sha256":"a733996ca18c2717572a20f99e3751687530ffb23dbda7e24adf606234a5d833"},"relative_symlink_policy":"INTERNAL_ONLY","release_version":"V2R1C4","schema_id":"AIOS_RUNTIME_LAYOUT_MANIFEST","schema_version":1,"special_file_policy":"FORBIDDEN","tree_algorithm_id":"AIOS_TREE_SHA256_V1"}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"anchor_socket": "/run/aios-v2r1c4-release-v5/anchor.sock",
|
||||
"approval_observer": {
|
||||
"activation_record_path": "/var/lib/aios-gitops/runtime/phase-a-gates/pr35-ff6743e8516834b19ed15ba36ca6755512fb0e91-20260731T092507Z/phase-a-activation-record.json",
|
||||
"credential_path": "/run/credentials/aios-v2r1c4-release-approval-observer-v5.service/token",
|
||||
"gitea_api_base_url": "http://127.0.0.1:3000/api/v1"
|
||||
},
|
||||
"authority": {
|
||||
"push_url": "file:///data/aios-gitops/repo.git",
|
||||
"repository_path": "/data/aios-gitops/repo.git"
|
||||
},
|
||||
"git": {
|
||||
"forbidden_mode_bits": 18,
|
||||
"owner_uid": 0,
|
||||
"path": "/usr/bin/git",
|
||||
"sha256": "2a8c18fbf43da9f692d75474c72bea9dfd796c260b0f3dfe456376abc3bbd668",
|
||||
"timeout_seconds": 120
|
||||
},
|
||||
"gitea": {
|
||||
"api_allowed_host": "127.0.0.1",
|
||||
"branch_credential_path": "/run/credentials/aios-v2r1c4-release-writer-v5.service/token",
|
||||
"branch_push_url": "https://g.cnnetmkt.top/git/aios/aios.git",
|
||||
"push_allowed_host": "g.cnnetmkt.top",
|
||||
"tag_credential_path": "/run/credentials/aios-v2r1c4-release-publisher-v5.service/token",
|
||||
"tag_push_url": "https://g.cnnetmkt.top/git/aios/aios.git"
|
||||
},
|
||||
"metadata_writer": {
|
||||
"api_bearer_credential_path": "/run/credentials/aios-v2r1c4-release-writer-v5.service/metadata-api-bearer",
|
||||
"gitea_api_base_url": "http://127.0.0.1:3000/api/v1",
|
||||
"gitea_publish_credential_path": "/run/credentials/aios-v2r1c4-release-writer-v5.service/gitea-publish-token",
|
||||
"gitea_repository_path": "/data/aios-gitops/runtime/gitea-bin/data/repositories/aios/aios.git",
|
||||
"listen_host": "127.0.0.1",
|
||||
"listen_port": 19892
|
||||
},
|
||||
"maximum_response_bytes": 2097152,
|
||||
"quarantine_root": "/var/lib/aios-v2r1c4-release-v5/quarantine",
|
||||
"repository_identity": "aios/aios",
|
||||
"timeout_seconds": 120
|
||||
}
|
||||
+1110
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
AIOS_V2R1C4_ANCHOR_UID=61200
|
||||
AIOS_V2R1C4_WRITER_UID=61201
|
||||
AIOS_V2R1C4_PUBLISHER_UID=61202
|
||||
AIOS_V2R1C4_CONTROLLER_UID=61203
|
||||
AIOS_V2R1C4_APPROVAL_OBSERVER_UID=61204
|
||||
AIOS_V2R1C4_ROLE_UIDS=61201,61202,61203
|
||||
+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())
|
||||
+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())
|
||||
+69
@@ -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}$"}
|
||||
}
|
||||
}
|
||||
+1
@@ -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"}}}
|
||||
@@ -0,0 +1,22 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=68"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "aios-task-scoped-release"
|
||||
version = "1.4.0"
|
||||
description = "Fail-closed task-scoped Writer, Publisher and production release controller for AIOS V2R1C4"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = []
|
||||
|
||||
[project.scripts]
|
||||
aios-task-release = "aios_task_scoped_release.cli:main"
|
||||
aios-task-release-anchor = "aios_task_scoped_release.anchor_service:main"
|
||||
aios-task-release-operation = "aios_task_scoped_release.operation_service:main"
|
||||
aios-task-release-approval-observer = "aios_task_scoped_release.approval_observer:main"
|
||||
|
||||
[tool.setuptools]
|
||||
package-dir = {"" = "src"}
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"$id": "https://aios.invalid/schemas/candidate-deployment-evidence-v1.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"artifact_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"evidence_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"candidate_active": {
|
||||
"const": true
|
||||
},
|
||||
"candidate_service": {
|
||||
"const": "aios-v2r1c4-candidate.service"
|
||||
},
|
||||
"production_unchanged": {
|
||||
"const": true
|
||||
},
|
||||
"production_unit_drop_in_paths": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"maxItems": 0,
|
||||
"type": "array"
|
||||
},
|
||||
"production_unit_fragment_path": {
|
||||
"pattern": "^/(?:etc|usr/lib)/systemd/system/[^/]+[.]service$",
|
||||
"type": "string"
|
||||
},
|
||||
"production_unit_mutable_path_conflicts": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"maxItems": 0,
|
||||
"type": "array"
|
||||
},
|
||||
"production_unit_no_new_privileges": {
|
||||
"const": true
|
||||
},
|
||||
"production_unit_protect_system": {
|
||||
"const": "strict"
|
||||
},
|
||||
"production_unit_read_only_paths": {
|
||||
"items": {
|
||||
"pattern": "^/",
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array",
|
||||
"uniqueItems": true
|
||||
},
|
||||
"production_unit_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"release_root_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"rollback_artifact_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"rollback_verified": {
|
||||
"const": true
|
||||
},
|
||||
"runtime_layout_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"const": "PASS"
|
||||
},
|
||||
"target_oid": {
|
||||
"pattern": "^[0-9a-f]{40}$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status",
|
||||
"target_oid",
|
||||
"artifact_sha256",
|
||||
"release_root_sha256",
|
||||
"rollback_artifact_sha256",
|
||||
"candidate_service",
|
||||
"candidate_active",
|
||||
"production_unchanged",
|
||||
"rollback_verified",
|
||||
"runtime_layout_sha256",
|
||||
"production_unit_sha256",
|
||||
"production_unit_fragment_path",
|
||||
"production_unit_drop_in_paths",
|
||||
"production_unit_no_new_privileges",
|
||||
"production_unit_protect_system",
|
||||
"production_unit_read_only_paths",
|
||||
"production_unit_mutable_path_conflicts",
|
||||
"evidence_sha256"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"$id": "https://aios.invalid/schemas/core-metadata-scan-evidence-v1.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"evidence_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"paths_scanned_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"raw_owner_id_occurrences": {
|
||||
"const": 0
|
||||
},
|
||||
"scan_status": {
|
||||
"enum": [
|
||||
"COMPLETE_NO_CORE_POSSIBLE",
|
||||
"COMPLETE_NO_CORE_FOUND",
|
||||
"COMPLETE_CORE_METADATA_SCANNED"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"const": "PASS"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status",
|
||||
"scan_status",
|
||||
"raw_owner_id_occurrences",
|
||||
"paths_scanned_sha256",
|
||||
"evidence_sha256"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"$id": "https://aios.invalid/schemas/evidence-closure-input-v1.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"core_metadata_evidence_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"incident_block_manifest_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"incident_block_record_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"provider_contract_evidence_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"pseudonymization_evidence_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"release_artifact_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"release_root_input_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"release_root_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"release_version": {
|
||||
"pattern": "^V[0-9]+R[0-9]+C[0-9]+$",
|
||||
"type": "string"
|
||||
},
|
||||
"repository_identity": {
|
||||
"const": "aios/aios"
|
||||
},
|
||||
"rollback_artifact_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"schema_id": {
|
||||
"const": "AIOS_EVIDENCE_CLOSURE_INPUT"
|
||||
},
|
||||
"schema_version": {
|
||||
"const": 1
|
||||
},
|
||||
"security_review_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"signature_witness_evidence_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"source_commit_oid": {
|
||||
"const": "1857b88690cd1c0119df984534c260b6f217a21a"
|
||||
},
|
||||
"source_date_epoch": {
|
||||
"minimum": 1,
|
||||
"type": "integer"
|
||||
},
|
||||
"source_tree_oid": {
|
||||
"const": "17c153de2338f570f70cb7407e7dda3c596b524b"
|
||||
},
|
||||
"test_summary_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"schema_id",
|
||||
"schema_version",
|
||||
"release_version",
|
||||
"repository_identity",
|
||||
"source_commit_oid",
|
||||
"source_tree_oid",
|
||||
"release_root_sha256",
|
||||
"release_root_input_sha256",
|
||||
"release_artifact_sha256",
|
||||
"rollback_artifact_sha256",
|
||||
"incident_block_record_sha256",
|
||||
"incident_block_manifest_sha256",
|
||||
"test_summary_sha256",
|
||||
"security_review_sha256",
|
||||
"provider_contract_evidence_sha256",
|
||||
"signature_witness_evidence_sha256",
|
||||
"pseudonymization_evidence_sha256",
|
||||
"core_metadata_evidence_sha256",
|
||||
"source_date_epoch"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
{
|
||||
"$id": "https://aios.invalid/schemas/evidence-closure-manifest-v1.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"algorithm_id": {
|
||||
"const": "AIOS_EVIDENCE_CLOSURE_V1"
|
||||
},
|
||||
"artifacts": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"release_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"rollback_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"release_sha256",
|
||||
"rollback_sha256"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"canonicalization": {
|
||||
"const": "AIOS_CANONICAL_JSON_V1"
|
||||
},
|
||||
"evidence": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"core_metadata_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"provider_contract_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"pseudonymization_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"security_review_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"signature_witness_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"test_summary_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"test_summary_sha256",
|
||||
"security_review_sha256",
|
||||
"provider_contract_sha256",
|
||||
"signature_witness_sha256",
|
||||
"pseudonymization_sha256",
|
||||
"core_metadata_sha256"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"incident": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"blocked_manifest_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"blocked_record_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"immutable": {
|
||||
"const": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"blocked_record_sha256",
|
||||
"blocked_manifest_sha256",
|
||||
"immutable"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"input_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"release_root": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"descriptor_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"input_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"descriptor_sha256",
|
||||
"input_sha256"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"release_version": {
|
||||
"pattern": "^V[0-9]+R[0-9]+C[0-9]+$",
|
||||
"type": "string"
|
||||
},
|
||||
"repository_identity": {
|
||||
"const": "aios/aios"
|
||||
},
|
||||
"schema_id": {
|
||||
"const": "AIOS_EVIDENCE_CLOSURE_MANIFEST"
|
||||
},
|
||||
"schema_version": {
|
||||
"const": 1
|
||||
},
|
||||
"source": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"commit_oid": {
|
||||
"pattern": "^[0-9a-f]{40}$",
|
||||
"type": "string"
|
||||
},
|
||||
"tree_oid": {
|
||||
"pattern": "^[0-9a-f]{40}$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"commit_oid",
|
||||
"tree_oid"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"source_date_epoch": {
|
||||
"minimum": 1,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"schema_id",
|
||||
"schema_version",
|
||||
"algorithm_id",
|
||||
"canonicalization",
|
||||
"release_version",
|
||||
"repository_identity",
|
||||
"source",
|
||||
"release_root",
|
||||
"artifacts",
|
||||
"incident",
|
||||
"evidence",
|
||||
"source_date_epoch",
|
||||
"input_sha256"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
{
|
||||
"$id": "https://aios.invalid/schemas/fresh-protected-dispatch-acceptance-v1.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"accepted": {
|
||||
"const": true
|
||||
},
|
||||
"canonical_request_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"canonical_request_sha256_recomputed": {
|
||||
"const": true
|
||||
},
|
||||
"ed25519_signature_verified": {
|
||||
"const": true
|
||||
},
|
||||
"effective_provider": {
|
||||
"const": "chatgpt-supervisor"
|
||||
},
|
||||
"event_id_hash": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"event_replay_checked": { "const": true },
|
||||
"event_replayed": { "const": false },
|
||||
"event_source": { "const": "WECHAT_NATURAL_INBOUND" },
|
||||
"evidence_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"external_executor_contacted": {
|
||||
"const": false
|
||||
},
|
||||
"inbound_pass": {
|
||||
"const": true
|
||||
},
|
||||
"natural_inbound": { "const": true },
|
||||
"replay_nonce_sha256": { "pattern": "^[0-9a-f]{64}$", "type": "string" },
|
||||
"deployment_observed_at": { "format": "date-time", "type": "string" },
|
||||
"inbound_received_at": { "format": "date-time", "type": "string" },
|
||||
"protected_dispatch_started_at": { "format": "date-time", "type": "string" },
|
||||
"reply_delivered_at": { "format": "date-time", "type": "string" },
|
||||
"observed_at": { "format": "date-time", "type": "string" },
|
||||
"deployment_target_oid": { "pattern": "^[0-9a-f]{40}$", "type": "string" },
|
||||
"runtime_layout_sha256": { "pattern": "^[0-9a-f]{64}$", "type": "string" },
|
||||
"production_unit_sha256": { "pattern": "^[0-9a-f]{64}$", "type": "string" },
|
||||
"production_service": { "const": "aios-v2r1c4-production.service" },
|
||||
"owner_principal": { "pattern": "^principal:sha256:[0-9a-f]{64}$", "type": "string" },
|
||||
"provider_receipt": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"event_id_hash": { "pattern": "^[0-9a-f]{64}$", "type": "string" },
|
||||
"nonce_sha256": { "pattern": "^[0-9a-f]{64}$", "type": "string" },
|
||||
"owner_principal": { "pattern": "^principal:sha256:[0-9a-f]{64}$", "type": "string" },
|
||||
"deployment_target_oid": { "pattern": "^[0-9a-f]{40}$", "type": "string" },
|
||||
"runtime_layout_sha256": { "pattern": "^[0-9a-f]{64}$", "type": "string" },
|
||||
"production_unit_sha256": { "pattern": "^[0-9a-f]{64}$", "type": "string" },
|
||||
"inbound_received_at": { "format": "date-time", "type": "string" },
|
||||
"protected_dispatch_started_at": { "format": "date-time", "type": "string" },
|
||||
"reply_delivered_at": { "format": "date-time", "type": "string" },
|
||||
"delivery_status": { "const": "DELIVERED" },
|
||||
"dispatch_mode": { "const": "PROTECTED_DISPATCH" }
|
||||
},
|
||||
"required": ["event_id_hash", "nonce_sha256", "owner_principal", "deployment_target_oid", "runtime_layout_sha256", "production_unit_sha256", "inbound_received_at", "protected_dispatch_started_at", "reply_delivered_at", "delivery_status", "dispatch_mode"],
|
||||
"type": "object"
|
||||
},
|
||||
"previous_block_manifest_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"previous_block_record_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"protected_dispatch_invoked": {
|
||||
"const": true
|
||||
},
|
||||
"provider_binding_enforced": {
|
||||
"const": true
|
||||
},
|
||||
"provider_binding_result": {
|
||||
"enum": [
|
||||
"DEFAULTED_TO_REQUIRED",
|
||||
"ALREADY_COMPLIANT",
|
||||
"OVERRIDDEN_TO_REQUIRED"
|
||||
]
|
||||
},
|
||||
"provider_hint_overridden": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"raw_owner_id_occurrences": {
|
||||
"const": 0
|
||||
},
|
||||
"real_user_impersonated": {
|
||||
"const": false
|
||||
},
|
||||
"receipt_cross_reference_verified": {
|
||||
"const": true
|
||||
},
|
||||
"receipt_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"reply_delivered": {
|
||||
"const": true
|
||||
},
|
||||
"required_provider": {
|
||||
"const": "chatgpt-supervisor"
|
||||
},
|
||||
"signature_payload_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"signature_payload_sha256_recomputed": {
|
||||
"const": true
|
||||
},
|
||||
"signature_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"simulation": {
|
||||
"const": true
|
||||
},
|
||||
"status": {
|
||||
"const": "PASS"
|
||||
},
|
||||
"unauthorized_write_occurred": {
|
||||
"const": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accepted",
|
||||
"canonical_request_sha256",
|
||||
"canonical_request_sha256_recomputed",
|
||||
"ed25519_signature_verified",
|
||||
"effective_provider",
|
||||
"event_id_hash",
|
||||
"event_replay_checked",
|
||||
"event_replayed",
|
||||
"event_source",
|
||||
"evidence_sha256",
|
||||
"external_executor_contacted",
|
||||
"inbound_pass",
|
||||
"natural_inbound",
|
||||
"replay_nonce_sha256",
|
||||
"deployment_observed_at",
|
||||
"inbound_received_at",
|
||||
"protected_dispatch_started_at",
|
||||
"reply_delivered_at",
|
||||
"observed_at",
|
||||
"deployment_target_oid",
|
||||
"runtime_layout_sha256",
|
||||
"production_unit_sha256",
|
||||
"production_service",
|
||||
"owner_principal",
|
||||
"provider_receipt",
|
||||
"previous_block_manifest_sha256",
|
||||
"previous_block_record_sha256",
|
||||
"protected_dispatch_invoked",
|
||||
"provider_binding_enforced",
|
||||
"provider_binding_result",
|
||||
"provider_hint_overridden",
|
||||
"raw_owner_id_occurrences",
|
||||
"real_user_impersonated",
|
||||
"receipt_cross_reference_verified",
|
||||
"receipt_sha256",
|
||||
"reply_delivered",
|
||||
"required_provider",
|
||||
"signature_payload_sha256",
|
||||
"signature_payload_sha256_recomputed",
|
||||
"signature_sha256",
|
||||
"simulation",
|
||||
"status",
|
||||
"unauthorized_write_occurred"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"$id": "https://aios.invalid/schemas/main-reconciliation-evidence-v1.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"authority_main_after_oid": {
|
||||
"pattern": "^[0-9a-f]{40}$",
|
||||
"type": "string"
|
||||
},
|
||||
"authority_main_before_oid": {
|
||||
"pattern": "^[0-9a-f]{40}$",
|
||||
"type": "string"
|
||||
},
|
||||
"evidence_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"fast_forward_verified": {
|
||||
"const": true
|
||||
},
|
||||
"gitea_api_main_oid": {
|
||||
"pattern": "^[0-9a-f]{40}$",
|
||||
"type": "string"
|
||||
},
|
||||
"gitea_main_after_oid": {
|
||||
"pattern": "^[0-9a-f]{40}$",
|
||||
"type": "string"
|
||||
},
|
||||
"gitea_main_oid": {
|
||||
"pattern": "^[0-9a-f]{40}$",
|
||||
"type": "string"
|
||||
},
|
||||
"source_approval_verified": {
|
||||
"const": true
|
||||
},
|
||||
"status": {
|
||||
"const": "PASS"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"authority_main_after_oid",
|
||||
"authority_main_before_oid",
|
||||
"evidence_sha256",
|
||||
"fast_forward_verified",
|
||||
"gitea_api_main_oid",
|
||||
"gitea_main_after_oid",
|
||||
"gitea_main_oid",
|
||||
"source_approval_verified",
|
||||
"status"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"$id": "https://aios.invalid/schemas/owner-identity-migration-evidence-v1.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"database_integrity": {
|
||||
"const": "PASS"
|
||||
},
|
||||
"event_relationships_preserved": {
|
||||
"const": true
|
||||
},
|
||||
"evidence_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"immutable_block_record_preserved": {
|
||||
"const": true
|
||||
},
|
||||
"pseudonym_consistency": {
|
||||
"const": "PASS"
|
||||
},
|
||||
"raw_owner_id_occurrences_after": {
|
||||
"const": 0
|
||||
},
|
||||
"raw_owner_id_occurrences_before": {
|
||||
"minimum": 1,
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"const": "PASS"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status",
|
||||
"raw_owner_id_occurrences_before",
|
||||
"raw_owner_id_occurrences_after",
|
||||
"pseudonym_consistency",
|
||||
"database_integrity",
|
||||
"event_relationships_preserved",
|
||||
"immutable_block_record_preserved",
|
||||
"evidence_sha256"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"$id": "https://aios.invalid/schemas/production-promotion-evidence-v1.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"artifact_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
|
||||
"candidate_stopped": {"const": true},
|
||||
"evidence_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
|
||||
"production_active": {"const": true},
|
||||
"production_promotion_receipt_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
|
||||
"production_service": {"const": "aios-v2r1c4-production.service"},
|
||||
"production_unit_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
|
||||
"release_root_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
|
||||
"rollback_artifact_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
|
||||
"rollback_verified": {"const": true},
|
||||
"runtime_layout_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
|
||||
"status": {"const": "PASS"},
|
||||
"target_oid": {"pattern": "^[0-9a-f]{40}$", "type": "string"}
|
||||
},
|
||||
"required": [
|
||||
"status", "target_oid", "artifact_sha256", "release_root_sha256",
|
||||
"rollback_artifact_sha256", "runtime_layout_sha256", "production_unit_sha256",
|
||||
"production_service", "production_active", "candidate_stopped", "rollback_verified",
|
||||
"production_promotion_receipt_sha256", "evidence_sha256"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"$id": "https://aios.invalid/schemas/publisher-unlock-v1.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"candidate_deployment_evidence_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"core_metadata_scan_evidence_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"fresh_owner_acceptance_evidence_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"identity_migration_evidence_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"main_reconciliation_evidence_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"manifest_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"publisher_unlocked": {
|
||||
"const": true
|
||||
},
|
||||
"schema_id": {
|
||||
"const": "AIOS_RELEASE_PUBLISHER_UNLOCK"
|
||||
},
|
||||
"schema_version": {
|
||||
"const": 1
|
||||
},
|
||||
"unlock_receipt_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"production_promotion_evidence_sha256": {
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"schema_id",
|
||||
"schema_version",
|
||||
"manifest_sha256",
|
||||
"main_reconciliation_evidence_sha256",
|
||||
"candidate_deployment_evidence_sha256",
|
||||
"identity_migration_evidence_sha256",
|
||||
"core_metadata_scan_evidence_sha256",
|
||||
"fresh_owner_acceptance_evidence_sha256",
|
||||
"publisher_unlocked",
|
||||
"unlock_receipt_sha256",
|
||||
"production_promotion_evidence_sha256"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user