From 5d0aff78b9e7b8a9d3f78815da7265c489c03f34 Mon Sep 17 00:00:00 2001 From: Ning Zhou Date: Mon, 17 Aug 2026 14:19:15 +0400 Subject: [PATCH] feat: gate Chongqing source governance decisions --- .github/workflows/cd-staging.yml | 1 + .github/workflows/ci.yml | 4 + data_agent/chongqing_source_governance.py | 369 ++++++++++++++++++ .../test_chongqing_source_governance.py | 107 +++++ ...dr-077-chongqing-source-governance-gate.md | 80 ++++ ...hongqing-source-governance-2026-08-17.json | 98 +++++ docs/roadmap.md | 5 +- docs/system-of-record-matrix-2026-07-24.md | 8 +- scripts/chongqing-source-governance.sh | 22 ++ 9 files changed, 689 insertions(+), 5 deletions(-) create mode 100644 data_agent/chongqing_source_governance.py create mode 100644 data_agent/test_chongqing_source_governance.py create mode 100644 docs/architecture-decisions/adr-077-chongqing-source-governance-gate.md create mode 100644 docs/evidence/chongqing-source-governance-2026-08-17.json create mode 100755 scripts/chongqing-source-governance.sh diff --git a/.github/workflows/cd-staging.yml b/.github/workflows/cd-staging.yml index 0dbe8ac1..333c741f 100644 --- a/.github/workflows/cd-staging.yml +++ b/.github/workflows/cd-staging.yml @@ -167,6 +167,7 @@ jobs: data_agent/test_auth.py \ data_agent/test_chongqing_real_source_admission.py \ data_agent/test_chongqing_extraction_provenance.py \ + data_agent/test_chongqing_source_governance.py \ data_agent/test_dolphinscheduler_adapter.py \ data_agent/test_dolphinscheduler_command_consumer.py \ data_agent/test_dolphinscheduler_command_worker.py \ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 30841553..86db6f86 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -130,6 +130,9 @@ jobs: - name: Validate Chongqing extraction provenance evidence run: python -m data_agent.chongqing_extraction_provenance + - name: Validate Chongqing source governance evidence + run: python -m data_agent.chongqing_source_governance + - name: Validate DolphinScheduler adapter boundary run: python -m data_agent.dolphinscheduler_adapter validate @@ -167,6 +170,7 @@ jobs: data_agent/test_auth.py \ data_agent/test_chongqing_real_source_admission.py \ data_agent/test_chongqing_extraction_provenance.py \ + data_agent/test_chongqing_source_governance.py \ data_agent/test_dolphinscheduler_adapter.py \ data_agent/test_dolphinscheduler_command_consumer.py \ data_agent/test_dolphinscheduler_command_worker.py \ diff --git a/data_agent/chongqing_source_governance.py b/data_agent/chongqing_source_governance.py new file mode 100644 index 00000000..798f4e59 --- /dev/null +++ b/data_agent/chongqing_source_governance.py @@ -0,0 +1,369 @@ +"""Validate the metadata-only governance gate for the first Chongqing source. + +M3-30 selects the first candidate land-parcel source and records the governance +decisions required before content admission. The checked evidence is a +fail-closed decision baseline: every decision is pending, and it never reads +or copies source payloads or creates Landing, Run, scheduler, or provider +authority. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import re +from collections.abc import Mapping +from pathlib import Path +from typing import Any + +from . import chongqing_extraction_provenance as provenance +from . import chongqing_real_source_admission as admission + +EVIDENCE_SCHEMA = "gda.chongqing_source_governance.v1" +VALIDATION_SCHEMA = "gda.chongqing_source_governance_validation.v1" +STATUS = "blocked_pending_governance_decisions" +SOURCE_ID = admission.SOURCE_ID +SOURCE_GROUP_ID = "bishan-planning-materials" +ASSET_ID = "bishan_land_use_dltb_local" +SOURCE_REF = f"source://{SOURCE_ID}/assets/{ASSET_ID}" + +REPO_ROOT = Path(__file__).resolve().parent.parent +DEFAULT_EVIDENCE_PATH = REPO_ROOT / ( + "docs/evidence/chongqing-source-governance-2026-08-17.json" +) + +UPSTREAM_ADMISSION_EVIDENCE_SHA256 = ( + "a2196495d845d61be939c7fc36a7f05c3567e365599d2d04be0aab9c568459c1" +) +UPSTREAM_ADMISSION_FILE_SHA256 = ( + "9b5c20369c235f7e0a2f2cb0a21cee77f86981aa273bac196605a4803b05ce83" +) +UPSTREAM_PROVENANCE_EVIDENCE_SHA256 = ( + "b56ce0c036827d4338ab2cfae8f3fb4c9e1e78ec18aac243272f1f77801300ef" +) +UPSTREAM_PROVENANCE_FILE_SHA256 = provenance.EVIDENCE_FILE_SHA256 +EVIDENCE_FILE_SHA256 = ( + "25bc5e2dfc5528f5556e7174f8c99fed7abaf30b9312528f5164c16bdf7cca9a" +) + +SHA256_PATTERN = re.compile(r"^[0-9a-f]{64}$") +DECISION_FIELDS = ( + "owner", + "license", + "retention", + "access", + "privacy_sensitivity", + "standard_version", + "data_slo", + "golden_result", +) +DECISION_RECORD_INVENTORY = {"status", "decision_ref", "attestation_sha256"} +DECISION_STATUSES = {"pending", "approved", "rejected"} + +EVIDENCE_INVENTORY = { + "schema", + "status", + "captured_at", + "source_binding", + "governance_decisions", + "blockers", + "admission_policy", + "claims", + "evidence_sha256", +} +SOURCE_BINDING_INVENTORY = { + "source_id", + "source_group_id", + "asset_id", + "source_ref", + "upstream_admission_evidence_sha256", + "upstream_admission_evidence_file_sha256", + "upstream_provenance_evidence_sha256", + "upstream_provenance_evidence_file_sha256", + "archive_sha256", + "extracted_payload_sha256", + "source_payload_in_evidence", + "absolute_source_paths_in_evidence", +} +ADMISSION_POLICY_INVENTORY = { + "metadata_governance_record_allowed", + "content_admission_requires_all_decisions", + "content_admission_requires_derivation_provenance", + "content_admission_requires_fresh_protected_attestation", + "source_payload_copy_to_repository_forbidden", + "local_profile_is_not_production_admission", + "landing_authority_creation_allowed", + "scheduler_submission_allowed", + "provider_mutation_allowed", +} +CLAIMS = { + "candidate_scope_selected", + "governance_decisions_complete", + "source_governance_approved", + "source_content_admitted", + "landing_authority_created", + "resource_version_created", + "platform_run_created", + "scheduler_submission_authorized", + "provider_mutation_authorized", + "production_ingestion_verified", + "production_ready", +} + + +class ChongqingSourceGovernanceError(RuntimeError): + """The M3-30 governance evidence failed closed.""" + + +def canonical_json_fingerprint(value: Any) -> str: + payload = json.dumps( + value, + ensure_ascii=True, + sort_keys=True, + separators=(",", ":"), + ).encode("utf-8") + return hashlib.sha256(payload).hexdigest() + + +def _file_sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for chunk in iter(lambda: stream.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def _load_json_object(path: Path) -> dict[str, Any]: + value = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(value, dict): + raise TypeError("JSON document is not an object") + return value + + +def _parse_time(value: Any) -> None: + try: + admission._parse_time(value) + except admission.ChongqingRealSourceAdmissionError as exc: + raise ChongqingSourceGovernanceError(str(exc)) from exc + + +def _expected_source_binding() -> dict[str, Any]: + return { + "source_id": SOURCE_ID, + "source_group_id": SOURCE_GROUP_ID, + "asset_id": ASSET_ID, + "source_ref": SOURCE_REF, + "upstream_admission_evidence_sha256": UPSTREAM_ADMISSION_EVIDENCE_SHA256, + "upstream_admission_evidence_file_sha256": UPSTREAM_ADMISSION_FILE_SHA256, + "upstream_provenance_evidence_sha256": UPSTREAM_PROVENANCE_EVIDENCE_SHA256, + "upstream_provenance_evidence_file_sha256": UPSTREAM_PROVENANCE_FILE_SHA256, + "archive_sha256": admission.EXPECTED_ARCHIVE_SHA256, + "extracted_payload_sha256": admission.EXPECTED_EXTRACTED_PAYLOAD_SHA256, + "source_payload_in_evidence": False, + "absolute_source_paths_in_evidence": False, + } + + +def _expected_governance_decisions() -> dict[str, dict[str, Any]]: + return { + field: { + "status": "pending", + "decision_ref": None, + "attestation_sha256": None, + } + for field in DECISION_FIELDS + } + + +def _expected_policy() -> dict[str, bool]: + return { + "metadata_governance_record_allowed": True, + "content_admission_requires_all_decisions": True, + "content_admission_requires_derivation_provenance": True, + "content_admission_requires_fresh_protected_attestation": True, + "source_payload_copy_to_repository_forbidden": True, + "local_profile_is_not_production_admission": True, + "landing_authority_creation_allowed": False, + "scheduler_submission_allowed": False, + "provider_mutation_allowed": False, + } + + +def _expected_claims() -> dict[str, bool]: + return { + "candidate_scope_selected": True, + "governance_decisions_complete": False, + "source_governance_approved": False, + "source_content_admitted": False, + "landing_authority_created": False, + "resource_version_created": False, + "platform_run_created": False, + "scheduler_submission_authorized": False, + "provider_mutation_authorized": False, + "production_ingestion_verified": False, + "production_ready": False, + } + + +def _expected_blockers() -> list[str]: + return [f"governance:{field}_decision_pending" for field in DECISION_FIELDS] + [ + "derivation:provenance_attestation_pending", + "source-governance:fresh_protected_attestation_pending", + ] + + +def _path_or_payload_findings(value: Any, prefix: str = "") -> list[str]: + findings = admission._sensitive_paths(value, prefix) + rendered = json.dumps(value, ensure_ascii=False, sort_keys=True) + for forbidden in ( + "/Users/", + "/private/", + "Downloads/", + "geometry_values", + "od_rows", + "flow_rows", + "local_source_path", + ): + if forbidden in rendered: + findings.append(f"forbidden:{forbidden}") + return sorted(set(findings)) + + +def validate_evidence(evidence: Mapping[str, Any]) -> list[str]: + errors: list[str] = [] + if set(evidence) != EVIDENCE_INVENTORY: + errors.append("M3-30 evidence inventory does not match") + stable = {key: value for key, value in evidence.items() if key != "evidence_sha256"} + if evidence.get("evidence_sha256") != canonical_json_fingerprint(stable): + errors.append("M3-30 evidence fingerprint does not match") + if evidence.get("schema") != EVIDENCE_SCHEMA or evidence.get("status") != STATUS: + errors.append("M3-30 evidence schema or status does not match") + try: + _parse_time(evidence.get("captured_at")) + except ChongqingSourceGovernanceError as exc: + errors.append(str(exc)) + + source = evidence.get("source_binding") + if not isinstance(source, Mapping) or set(source) != SOURCE_BINDING_INVENTORY: + errors.append("M3-30 source binding inventory does not match") + source = {} + for key, expected in _expected_source_binding().items(): + if source.get(key) != expected: + errors.append(f"M3-30 source binding does not match: {key}") + for key in ( + "upstream_admission_evidence_sha256", + "upstream_admission_evidence_file_sha256", + "upstream_provenance_evidence_sha256", + "upstream_provenance_evidence_file_sha256", + "archive_sha256", + "extracted_payload_sha256", + ): + if not SHA256_PATTERN.fullmatch(str(source.get(key) or "")): + errors.append(f"M3-30 source fingerprint is invalid: {key}") + if not admission.SOURCE_REF_PATTERN.fullmatch(str(source.get("source_ref") or "")): + errors.append("M3-30 source reference is invalid") + + decisions = evidence.get("governance_decisions") + if not isinstance(decisions, Mapping) or set(decisions) != set(DECISION_FIELDS): + errors.append("M3-30 governance decision inventory does not match") + decisions = {} + for field, expected in _expected_governance_decisions().items(): + record = decisions.get(field) + if not isinstance(record, Mapping) or set(record) != DECISION_RECORD_INVENTORY: + errors.append(f"M3-30 decision record does not match: {field}") + continue + if dict(record) != expected: + errors.append(f"M3-30 decision remains unresolved: {field}") + if record.get("status") not in DECISION_STATUSES: + errors.append(f"M3-30 decision status is invalid: {field}") + attestation = record.get("attestation_sha256") + if attestation is not None and not SHA256_PATTERN.fullmatch(str(attestation)): + errors.append(f"M3-30 decision attestation is invalid: {field}") + + blockers = evidence.get("blockers") + if blockers != _expected_blockers(): + errors.append("M3-30 blocker inventory does not match") + + policy = evidence.get("admission_policy") + if not isinstance(policy, Mapping) or set(policy) != ADMISSION_POLICY_INVENTORY: + errors.append("M3-30 admission policy inventory does not match") + policy = {} + for key, expected in _expected_policy().items(): + if policy.get(key) is not expected: + errors.append(f"M3-30 admission policy does not match: {key}") + + claims = evidence.get("claims") + expected_claims = _expected_claims() + if not isinstance(claims, Mapping) or set(claims) != CLAIMS: + errors.append("M3-30 claims inventory does not match") + else: + for key, expected in expected_claims.items(): + if claims.get(key) is not expected: + errors.append(f"M3-30 claim does not match: {key}") + + findings = _path_or_payload_findings(evidence) + if findings: + errors.append("M3-30 evidence contains a path or payload marker") + return sorted(set(errors)) + + +def build_validation_report( + evidence_path: Path = DEFAULT_EVIDENCE_PATH, +) -> dict[str, Any]: + try: + file_sha256 = _file_sha256(evidence_path) + evidence = _load_json_object(evidence_path) + errors = validate_evidence(evidence) + if EVIDENCE_FILE_SHA256 and file_sha256 != EVIDENCE_FILE_SHA256: + errors.append("M3-30 evidence file fingerprint does not match") + except (OSError, TypeError, ValueError, json.JSONDecodeError) as exc: + evidence = {} + file_sha256 = None + errors = [f"M3-30 evidence is unreadable: {type(exc).__name__}"] + claims = evidence.get("claims") + return { + "schema": VALIDATION_SCHEMA, + "status": "valid" if not errors else "invalid", + "errors": sorted(set(errors)), + "evidence_file_sha256": file_sha256, + "evidence_sha256": evidence.get("evidence_sha256"), + "source_id": evidence.get("source_binding", {}).get("source_id") + if isinstance(evidence.get("source_binding"), Mapping) + else None, + "candidate_asset_id": evidence.get("source_binding", {}).get("asset_id") + if isinstance(evidence.get("source_binding"), Mapping) + else None, + "pending_decision_count": sum( + 1 + for record in evidence.get("governance_decisions", {}).values() + if isinstance(record, Mapping) and record.get("status") == "pending" + ) + if isinstance(evidence.get("governance_decisions"), Mapping) + else None, + "source_governance_approved": ( + claims.get("source_governance_approved") + if isinstance(claims, Mapping) + else None + ), + "source_content_admitted": ( + claims.get("source_content_admitted") if isinstance(claims, Mapping) else None + ), + } + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--evidence", type=Path, default=DEFAULT_EVIDENCE_PATH) + args = parser.parse_args(argv) + try: + report = build_validation_report(args.evidence) + except ChongqingSourceGovernanceError as exc: + print(f"Chongqing source governance: {exc}") + return 1 + print(json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True)) + return 0 if report["status"] == "valid" else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/data_agent/test_chongqing_source_governance.py b/data_agent/test_chongqing_source_governance.py new file mode 100644 index 00000000..b0b9025e --- /dev/null +++ b/data_agent/test_chongqing_source_governance.py @@ -0,0 +1,107 @@ +import json +from copy import deepcopy + +from data_agent import chongqing_source_governance as governance + + +def _evidence() -> dict: + return json.loads(governance.DEFAULT_EVIDENCE_PATH.read_text(encoding="utf-8")) + + +def _rehash(value: dict) -> None: + stable = {key: item for key, item in value.items() if key != "evidence_sha256"} + value["evidence_sha256"] = governance.canonical_json_fingerprint(stable) + + +def test_checked_governance_baseline_is_valid_but_blocked(): + evidence = _evidence() + + assert governance.validate_evidence(evidence) == [] + report = governance.build_validation_report() + + assert report["status"] == "valid" + assert report["candidate_asset_id"] == "bishan_land_use_dltb_local" + assert report["pending_decision_count"] == 8 + assert report["source_governance_approved"] is False + assert report["source_content_admitted"] is False + + +def test_governance_binds_both_upstream_evidence_layers(): + source = _evidence()["source_binding"] + + assert source["upstream_admission_evidence_sha256"] == ( + "a2196495d845d61be939c7fc36a7f05c3567e365599d2d04be0aab9c568459c1" + ) + assert source["upstream_provenance_evidence_sha256"] == ( + "b56ce0c036827d4338ab2cfae8f3fb4c9e1e78ec18aac243272f1f77801300ef" + ) + + +def test_all_required_governance_decisions_are_pending(): + decisions = _evidence()["governance_decisions"] + + assert tuple(decisions) == governance.DECISION_FIELDS + assert all(record["status"] == "pending" for record in decisions.values()) + assert all(record["decision_ref"] is None for record in decisions.values()) + assert all(record["attestation_sha256"] is None for record in decisions.values()) + + +def test_rehash_cannot_turn_governance_into_admission(): + evidence = deepcopy(_evidence()) + evidence["claims"]["governance_decisions_complete"] = True + evidence["claims"]["source_governance_approved"] = True + evidence["claims"]["source_content_admitted"] = True + _rehash(evidence) + + errors = governance.validate_evidence(evidence) + + assert "M3-30 claim does not match: governance_decisions_complete" in errors + assert "M3-30 claim does not match: source_governance_approved" in errors + assert "M3-30 claim does not match: source_content_admitted" in errors + + +def test_decision_attestation_must_be_a_sha256(): + evidence = deepcopy(_evidence()) + evidence["governance_decisions"]["owner"]["attestation_sha256"] = "not-a-sha" + _rehash(evidence) + + errors = governance.validate_evidence(evidence) + + assert "M3-30 decision attestation is invalid: owner" in errors + + +def test_upstream_provenance_fingerprint_cannot_drift(): + evidence = deepcopy(_evidence()) + evidence["source_binding"]["upstream_provenance_evidence_sha256"] = "0" * 64 + _rehash(evidence) + + errors = governance.validate_evidence(evidence) + + assert "M3-30 source binding does not match: upstream_provenance_evidence_sha256" in errors + + +def test_path_or_payload_marker_is_rejected_even_after_rehash(): + evidence = deepcopy(_evidence()) + evidence["governance_decisions"]["owner"]["decision_ref"] = "/private/approval.txt" + _rehash(evidence) + + errors = governance.validate_evidence(evidence) + + assert "M3-30 evidence contains a path or payload marker" in errors + + +def test_policy_keeps_all_side_effect_authority_false(): + policy = _evidence()["admission_policy"] + + assert policy["metadata_governance_record_allowed"] is True + assert policy["landing_authority_creation_allowed"] is False + assert policy["scheduler_submission_allowed"] is False + assert policy["provider_mutation_allowed"] is False + + +def test_validator_report_is_path_free(): + report = governance.build_validation_report() + rendered = json.dumps(report, ensure_ascii=False) + + assert "/private/" not in rendered + assert "/Users/" not in rendered diff --git a/docs/architecture-decisions/adr-077-chongqing-source-governance-gate.md b/docs/architecture-decisions/adr-077-chongqing-source-governance-gate.md new file mode 100644 index 00000000..3d8ec75a --- /dev/null +++ b/docs/architecture-decisions/adr-077-chongqing-source-governance-gate.md @@ -0,0 +1,80 @@ +# ADR-077: Chongqing source governance gate + +**Status**: Accepted + +**Date**: 2026-08-17 + +**Decision owners**: Data Platform, Data Governance, Security, Data Product + +## Context + +M3-28 binds the full Chongqing source corpus through metadata-only physical and +logical fingerprints. M3-29 makes the unresolved archive-to-working-set +derivation explicit. Neither record identifies which asset should enter the +first land-parcel vertical slice or provides owner, license, retention, access, +privacy/sensitivity, standard-version, DataSLO, or golden-result decisions. + +Selecting a candidate is not equivalent to approving its content. Leaving the +required governance fields only in roadmap prose would allow later ingestion +code to omit a decision or treat an informal note as authority. + +## Decision + +Adopt M3-30 as a separate, immutable and metadata-only governance baseline for +`source://chongqing-planning-institute-sample/assets/bishan_land_use_dltb_local`. +The evidence binds the M3-28 admission fingerprints, the M3-29 provenance +fingerprints, the source group, asset ID, archive fingerprint, and extracted +working-set fingerprint. + +The gate enumerates eight independently required decisions: + +- owner; +- license; +- retention; +- access; +- privacy and sensitivity; +- standard version; +- DataSLO; +- golden result. + +Each decision is represented by a status, a decision reference, and an +attestation fingerprint. In the checked baseline all eight statuses are +`pending`, all references and attestations are absent, and the exact blocker +inventory is CI validated. A fresh protected attestation is required even after +all decision records exist. + +## Authority boundary + +M3-30 selects the first candidate scope only. It does not approve source +governance, complete derivation provenance, admit content, create an immutable +Landing object, ResourceVersion, PlatformRun, scheduler command, provider +mutation, lakehouse table, serving projection, or production ingestion. + +The evidence contains no source payload, absolute path, record value, or +geometry. It cannot be edited or rehashed into an approval. Later admission +must consume separate signed decision attestations and complete M3-29 +derivation evidence, then run through a protected fail-closed admission path. + +## Consequences + +**Positive**: the first AR-2 land-parcel candidate and every governance input +required for admission are now machine-checkable platform facts. + +**Positive**: CI rejects missing fields, fingerprint drift, path/payload +markers, and any attempt to turn pending decisions into admission authority. + +**Negative**: AR-2 remains `in_progress`. Eight governance decisions, six +derivation inputs, and a fresh protected attestation are still external +blockers. + +## Verification + +```bash +./scripts/chongqing-source-governance.sh +python -m pytest data_agent/test_chongqing_source_governance.py -q +``` + +The checked evidence fingerprint is +`97cf11ab8938c048dce9db903d1a4f30758f208dec6dad1a08b740a4a8fe7b6f`; its +file SHA-256 is +`25bc5e2dfc5528f5556e7174f8c99fed7abaf30b9312528f5164c16bdf7cca9a`. diff --git a/docs/evidence/chongqing-source-governance-2026-08-17.json b/docs/evidence/chongqing-source-governance-2026-08-17.json new file mode 100644 index 00000000..ffa0385b --- /dev/null +++ b/docs/evidence/chongqing-source-governance-2026-08-17.json @@ -0,0 +1,98 @@ +{ + "schema": "gda.chongqing_source_governance.v1", + "status": "blocked_pending_governance_decisions", + "captured_at": "2026-08-17T10:00:00Z", + "source_binding": { + "source_id": "chongqing-planning-institute-sample", + "source_group_id": "bishan-planning-materials", + "asset_id": "bishan_land_use_dltb_local", + "source_ref": "source://chongqing-planning-institute-sample/assets/bishan_land_use_dltb_local", + "upstream_admission_evidence_sha256": "a2196495d845d61be939c7fc36a7f05c3567e365599d2d04be0aab9c568459c1", + "upstream_admission_evidence_file_sha256": "9b5c20369c235f7e0a2f2cb0a21cee77f86981aa273bac196605a4803b05ce83", + "upstream_provenance_evidence_sha256": "b56ce0c036827d4338ab2cfae8f3fb4c9e1e78ec18aac243272f1f77801300ef", + "upstream_provenance_evidence_file_sha256": "cfae0478c76452a155e8af42ec8499e4e7876a49c1dbb98648526025cb154360", + "archive_sha256": "2043b60c2f4f7f32a31388a634fae4ac28534990e205aa86b8df0e4b64dcbbca", + "extracted_payload_sha256": "e7e81e4f53f9f174792f500fbfdfde6bee30ec03beac8cbd91771fe09f548ea6", + "source_payload_in_evidence": false, + "absolute_source_paths_in_evidence": false + }, + "governance_decisions": { + "owner": { + "status": "pending", + "decision_ref": null, + "attestation_sha256": null + }, + "license": { + "status": "pending", + "decision_ref": null, + "attestation_sha256": null + }, + "retention": { + "status": "pending", + "decision_ref": null, + "attestation_sha256": null + }, + "access": { + "status": "pending", + "decision_ref": null, + "attestation_sha256": null + }, + "privacy_sensitivity": { + "status": "pending", + "decision_ref": null, + "attestation_sha256": null + }, + "standard_version": { + "status": "pending", + "decision_ref": null, + "attestation_sha256": null + }, + "data_slo": { + "status": "pending", + "decision_ref": null, + "attestation_sha256": null + }, + "golden_result": { + "status": "pending", + "decision_ref": null, + "attestation_sha256": null + } + }, + "blockers": [ + "governance:owner_decision_pending", + "governance:license_decision_pending", + "governance:retention_decision_pending", + "governance:access_decision_pending", + "governance:privacy_sensitivity_decision_pending", + "governance:standard_version_decision_pending", + "governance:data_slo_decision_pending", + "governance:golden_result_decision_pending", + "derivation:provenance_attestation_pending", + "source-governance:fresh_protected_attestation_pending" + ], + "admission_policy": { + "metadata_governance_record_allowed": true, + "content_admission_requires_all_decisions": true, + "content_admission_requires_derivation_provenance": true, + "content_admission_requires_fresh_protected_attestation": true, + "source_payload_copy_to_repository_forbidden": true, + "local_profile_is_not_production_admission": true, + "landing_authority_creation_allowed": false, + "scheduler_submission_allowed": false, + "provider_mutation_allowed": false + }, + "claims": { + "candidate_scope_selected": true, + "governance_decisions_complete": false, + "source_governance_approved": false, + "source_content_admitted": false, + "landing_authority_created": false, + "resource_version_created": false, + "platform_run_created": false, + "scheduler_submission_authorized": false, + "provider_mutation_authorized": false, + "production_ingestion_verified": false, + "production_ready": false + }, + "evidence_sha256": "97cf11ab8938c048dce9db903d1a4f30758f208dec6dad1a08b740a4a8fe7b6f" +} diff --git a/docs/roadmap.md b/docs/roadmap.md index f8f3ea46..0577994d 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -441,6 +441,7 @@ AR-0 Architecture / Schema / Runtime Truth - 真实源 admission contract:以不可变 archive checksum、解压 payload fingerprint、source-group manifest、metadata profile 和治理 blocker 建立准入基线;证据不得含源 payload、绝对路径、记录值或 geometry,profiling 不等于 content admission。 - M3-29 extraction provenance contract:以 M3-28 上游 evidence fingerprint、archive/extracted comparison 和明确的 derivation blocker 固定“已观察比较”与“派生证明缺失”的边界;operator、tool、command、modified/additional manifest 和 archive-to-working-set attestation 未齐全前不得 content admission。 +- M3-30 source governance gate:选择 `bishan_land_use_dltb_local` 作为首条地类图斑候选,绑定 M3-28/M3-29 指纹并把 owner、license、retention、access、privacy/sensitivity、标准版本、DataSLO、golden result 八项决策冻结为独立 pending records;完整派生证明、签名决策和 fresh protected attestation 未齐全前不得 content admission。 - SourceDefinition、CredentialReference、SourceCapability、SyncDefinition/Version、SyncRun、Cursor/Watermark、SchemaDriftEvent 和 Reconciliation。 - 数据库、对象存储/空间文件、HTTP/STAC 三类代表 source 的连接、凭据、连通、发现、preview、profile 和 owner 登记。 - 全量/增量微批的 Append/Overwrite/Merge 策略,以及至少一个真实 CDC 或事件流 source 通过 Flink 写入版本化 Bronze;覆盖 watermark/offset、checkpoint、迟到/乱序、源端删除、幂等、对账、重放和失败恢复。 @@ -688,7 +689,7 @@ Golden checks 至少覆盖: 3. 冻结 ResourceURN、ResourceVersion、PlatformDefinition/PlatformRun/FrameworkAttemptObservation/Artifact/LineageEvent、SubjectContext 与 storage/table/compute provider 最小合同。 4. 分阶段实现 `gda-metadata-fabric-bridge`:M1 只读 mapping/reconciliation、M2a 本地 foundation/重启连续性、M2b-1 本地三存储恢复、M2b-2 隔离 versioned/Object-Locked repository round-trip、M2b-3 本机双集群 + Kubernetes 外 COMPLIANCE repository + 独立 writer/reader、M2c-1 provider-native metrics、M2c-2 临时 OTel Collector + JSON Exporter 的双周期本地 pipeline、M2c-3 本地单 job scrape 故障检测/配置恢复/完整清理 evidence、M2c-4 绑定 source revision 的 production observability readiness contract,以及 M2d-1 本地 kindnet 跨节点 NetworkPolicy enforcement 已验证;M2c-4 当前仍有 20 项 blockers,M2d-1 也未验证生产 provider policy 或 tenant isolation。下一步完成 source host/cluster 外的生产 bucket、KMS/TLS/workload identity、source-loss recovery 与 RPO/RTO,并批准 metrics backend、retention、OTel/TLS、tenant、alert/SLO/owner 后在受保护环境验证持续采集、存储、查询、真实告警投递、runbook 响应和 provider NetworkPolicy;再推进 OIDC、upgrade/rollback、registry provenance 和 owner/runbook;之后才进入 M3 ingestion/OpenLineage/conformance。 5. 实现 `gda-orchestration-gateway`、DolphinScheduler process/task/schedule/complement/worker-group、Spark/Flink provider task adapter 和故障注入;不再开发新的 lease/queue/scheduler。 -6. M3-28 已冻结全量重庆真实源的 path-free physical/metadata admission baseline,M3-29 已建立 metadata-only extraction provenance gap baseline;下一步取得 operator/tool/command、modified/additional manifest 与 archive-to-working-set attestation,并由 owner 决定首条地类图斑源的 license、retention、access、privacy/sensitivity、标准版本、SLO 和 golden result,未获批准前不得 content admission。 +6. M3-28 已冻结全量重庆真实源的 path-free physical/metadata admission baseline,M3-29 已建立 metadata-only extraction provenance gap baseline,M3-30 已选择 `bishan_land_use_dltb_local` 并把八项治理输入固化为 fail-closed pending records;下一步取得 operator/tool/command、modified/additional manifest、archive-to-working-set attestation、八项签名治理决策与 fresh protected attestation,未获批准前不得 content admission。 7. 冻结 Default Lakehouse、Cloud Managed、Lightweight Integrated profiles;以统一 Run 完成默认 MinIO/Iceberg/Spark/Flink、轻量 PostGIS/DuckDB 和 Azure 代表 adapter 的 conformance smoke。 8. 实现跨 profile 的 Raw -> ODS -> DIM/DWD -> DWS -> ADS 通用生产、质量、发布、回滚和 golden equivalence。 9. 建立 DataProductBlueprint、模型版本和 Visual/SQL/Notebook 共用 definition 的 Build 工作台,打通 preview、test、publish、approval 和 rollback。 @@ -729,7 +730,7 @@ AR-4 parity/control gate 退出前暂停以下主线扩张: |---|---|---| | AR-0 Architecture/Schema/Runtime Truth Freeze | `in_progress` | 全环境 schema/config fingerprint、迁移 fail-closed、事实清单、storage/compute/GIS serving provider profile/capability、ADR-017 benchmark、owner/SLO 和首条数据/服务验收集冻结 | | AR-1 Unified Metadata + Orchestration Control Planes | `in_progress` | controlled gateway、DolphinScheduler adapter、Metadata Fabric M1/M2a/M2b、M2c-1 provider metrics、M2c-2 本地临时 OTel pipeline、M2c-3 本地 scrape failure/recovery、M2c-4 production observability readiness contract 与 M2d-1 本地跨节点 NetworkPolicy enforcement 已验证,生产观测和生产 policy/tenant isolation 仍 blocked;下一证据是 source host/cluster 外的生产 recovery、持久 metrics backend/TLS/tenant/真实 alert delivery/SLO、OIDC、受保护 provider NetworkPolicy、升级回滚/registry provenance,以及受控 ingestion/replay 与无双写验收 | -| AR-2 Source/Ingestion + Geospatial Lakehouse Vertical Slice | `in_progress` | M3-28 admission baseline 与 M3-29 metadata-only extraction provenance gap baseline 已完成;下一证据是 operator/tool/command、modified/additional manifest、archive-to-working-set attestation,以及 owner 对 license、retention、access、privacy/sensitivity、标准版本、SLO 和 golden result 的治理决策,随后才可授权 immutable Landing authority、content admission 和 ingestion;最终仍需三类代表源、`DriveTransfer` 云盘客户端、大文件恢复、默认湖仓、轻量存算一体及 Azure 代表 adapter 通过 provider conformance 与 Raw -> ADS 验收 | +| AR-2 Source/Ingestion + Geospatial Lakehouse Vertical Slice | `in_progress` | M3-28 admission baseline、M3-29 extraction provenance gap baseline 与 M3-30 first-candidate governance gate 已完成;下一证据是 operator/tool/command、modified/additional manifest、archive-to-working-set attestation、八项签名治理决策与 fresh protected attestation,随后才可授权 immutable Landing authority、content admission 和 ingestion;最终仍需三类代表源、`DriveTransfer` 云盘客户端、大文件恢复、默认湖仓、轻量存算一体及 Azure 代表 adapter 通过 provider conformance 与 Raw -> ADS 验收 | | AR-3 Data Product Engineering + Governance Workbench | `planned` | Blueprint、模型、Visual/SQL/Notebook、DataOps CI/CD、质量/安全/审批共用 definition 和产品生命周期 | | AR-4 Asset/GIS Service/Spatial Experience Operations | `planned` | Service Control Plane、Features/Tiles/MVT/COG/STAC/export 及条件 legacy OGC/3D/EDR provider、Gateway/权限/缓存、原子切换/回滚、Discover/Operate/Govern 和无 LLM 多入口通过 conformance/parity/control gate | | AR-5 AgentOps Runtime + UX Uplift | `planned` | DataOps parity/control 通过;Agent bundle eval、deployment、online observation、incident/rollback 和 uplift gate | diff --git a/docs/system-of-record-matrix-2026-07-24.md b/docs/system-of-record-matrix-2026-07-24.md index 72ebe8ad..9bcee9ec 100644 --- a/docs/system-of-record-matrix-2026-07-24.md +++ b/docs/system-of-record-matrix-2026-07-24.md @@ -2,7 +2,7 @@ 日期:2026-08-17 -阶段:AR-0 `in_progress`;AR-1 gateway、成功终局 evidence gate、DolphinScheduler adapter sandbox POC、Metadata Fabric M1/M2、M2c-4/M2d-2 production readiness contracts、M3-1/M3-2、M3-3 local binding ledger、M3-4 local OpenLineage wire delivery、M3-5 local OpenMetadata bounded identity、M3-6 local Gravitino Basic bounded identity 与 M3-7 production identity readiness contract 已验证;AR-2 M3-28 重庆真实源 metadata-only admission baseline 与 M3-29 extraction provenance gap baseline 已建立,内容准入、Landing authority、生产 provider ingestion、生产观测、生产 policy/tenant isolation、生产 identity attestation 和生产切换仍 `in_progress` +阶段:AR-0 `in_progress`;AR-1 gateway、成功终局 evidence gate、DolphinScheduler adapter sandbox POC、Metadata Fabric M1/M2、M2c-4/M2d-2 production readiness contracts、M3-1/M3-2、M3-3 local binding ledger、M3-4 local OpenLineage wire delivery、M3-5 local OpenMetadata bounded identity、M3-6 local Gravitino Basic bounded identity 与 M3-7 production identity readiness contract 已验证;AR-2 M3-28 重庆真实源 metadata-only admission baseline、M3-29 extraction provenance gap baseline 与 M3-30 first-candidate governance gate 已建立,内容准入、Landing authority、生产 provider ingestion、生产观测、生产 policy/tenant isolation、生产 identity attestation 和生产切换仍 `in_progress` 适用分支:`main` @@ -21,7 +21,7 @@ | 部署配置策略 | Compose/K8s/进程环境;`platform_truth.CONFIG_SPECS` 定义关键类型与策略;DolphinScheduler worker 有默认零副本、外部 ConfigMap/Secret 驱动的 Kustomize 模板、静态 validator、staging activation preflight 和受保护的单副本 activation admission/workflow | `.env` 仅补默认;脱敏 snapshot、Secret key attestation、未扩容 Deployment、`ready_for_activation` 和未执行的 activation workflow 都是观测/变更能力 | 版本化 DeploymentProfile + secret reference;部署环境始终优先;模板、preflight 或 admission 通过都不等于环境已启用 | Platform/SRE/Security | AR-0,部分实现;worker 激活合同已验证、真实运行待审批 | | 环境发布与晋级 | publisher `31862363442`、protected verifier `31862984294` 与 staging deploy/observe `31863077257` 已将 `main@5fffc85`、GHCR digest、attested release、cluster/namespace identity 和 live revision 绑定;schema/config/runtime/health/rollout 通过,golden slice 缺失使 promotion fail closed | 旧 mainline、feature branch、CI artifact、JSON、离线 report、单独的 staging deployment 或人工批准都不能成为 production 发布权威 | 由受保护 environment 的 DeploymentRevision 绑定 OCI、provenance artifact、release manifest、golden slice 与全部 live verdict | Platform/SRE/Security/Repository Owner | AR-1 真实 staging 已部署 -> golden slice/production exit gates 待完成 | | 后台运行时清单 | `platform_truth.RUNTIME_INVENTORY` 是代码层登记;`gda_control` 已有受控 PlatformRun 写入口;DolphinScheduler adapter、tenant-scoped managed command worker 与受保护的单副本激活边界已有代码和测试,但 worker 尚未在 staging 运行;M2b recovery runner、M2c-1 provider probe、M2c-2 `_OtelPortForward`、M2c-3 failure rehearsal 与 M2d-1 NetworkPolicy rehearsal 均登记为 `local_verification_only`,不是 scheduler、worker、持续监控、生产 policy controller 或状态权威 | AST primitive report、worker status JSON、FrameworkAttemptObservation、DolphinScheduler instance state、本地 recovery/metrics/network-policy evidence | PlatformRun ledger 唯一登记最终状态;framework/provider attempt 只能回报观测;worker status 仅为进程健康投影;本地演练进程与 evidence 不得变成生产控制器、监控后端或 tenant-isolation 权威 | Platform Architecture | AR-1 adapter/worker/activation 合同已验证 -> staging 运行待审批;metadata recovery/metrics/policy runner 仅本地验证 | -| 原始文件/对象 | M3-28 已以 path-free metadata manifest 记录重庆真实源 archive/extracted fingerprints、source groups、asset profiles 与治理 blockers;M3-29 进一步记录 526 exact、6 modified、0 missing、52 additional 的比较及 6 项派生证明缺口;`source_content_admitted=false`,因此尚无内容写入权威 | admission/provenance evidence JSON、archive/extracted comparison、临时上传、下载缓存、预览文件 | 在 operator/tool/command、modified/additional manifest、archive-to-working-set attestation、owner/license/retention/access/privacy 等决策进入正式 authority 后,才可由 immutable Landing object URI + checksum + retention 建立内容权威;本地 scratch 与 checked evidence 均不可替代 Landing | Data Platform | AR-2 `in_progress` | +| 原始文件/对象 | M3-28 已以 path-free metadata manifest 记录重庆真实源 archive/extracted fingerprints、source groups、asset profiles 与治理 blockers;M3-29 进一步记录 526 exact、6 modified、0 missing、52 additional 的比较及 6 项派生证明缺口;M3-30 已选择首条地类图斑候选并把八项治理决策固定为 pending records;`source_content_admitted=false`,因此尚无内容写入权威 | admission/provenance/governance evidence JSON、archive/extracted comparison、临时上传、下载缓存、预览文件 | 在 operator/tool/command、modified/additional manifest、archive-to-working-set attestation、八项签名治理决策与 fresh protected attestation 进入正式 authority 后,才可由 immutable Landing object URI + checksum + retention 建立内容权威;本地 scratch 与 checked evidence 均不可替代 Landing | Data Platform | AR-2 `in_progress` | | 湖仓表与 snapshot | Iceberg/STAC/S3A 有局部实现,尚无通用发布权威 | STAC item、GeoParquet export | Iceberg catalog snapshot 是分析表版本权威;对象是物理内容,STAC 是发现投影 | Data Platform | AR-2 | | 在线空间数据 | PostGIS 业务表是当前编辑/查询事实,部分临时表混入 | Martin MVT、API JSON、导出文件 | 已批准 DataProductVersion 物化到 PostGIS;不能由瓦片或临时表反向定义产品版本 | GIS/Data Platform | AR-2 -> AR-4 | | 数据资产身份与版本 | `gda_control.resource/resource_version` 已实现 identity、hash、predecessor、tenant FK 和幂等 gateway 写入;`agent_data_assets`、`agent_asset_versions` 仍是兼容写路径 | UI catalog、search index、STAC | GDA ledger 管身份与版本绑定;旧行只有在 tenant、authority identity、checksum 和 version evidence 完整时才可形成 eligible plan;OpenMetadata 管治理目录,Gravitino 管技术对象映射 | Metadata Platform | AR-1 gateway 已验证 -> 生产切换待验收 | @@ -64,6 +64,7 @@ 18. Metadata Fabric M3-5 只证明临时 OpenMetadata bot 在 provider 强制 `DefaultBotRole` 之上的项目新增 grant 是 `table/Create`,以及 policy-create 拒绝、本地 JWT 轮换/吊销和 cleanup;bootstrap provisioner、临时 identity、合成 attestation 和 local evidence 都不等于双 provider/生产最小权限、protected workload identity/OIDC、持久 credential delivery、生产 tenant isolation、生产 ingestion/conformance 或生产写权威。 19. M3-28 只允许 path-free、metadata-only 的重庆真实源 admission;archive/extracted fingerprint、source-group manifest、asset profile 和 57 个治理 blocker 形成准入观察,不形成 Landing object、ResourceVersion、PlatformRun、授权 Artifact、scheduler submission、provider mutation、content admission 或 production ingestion 权威;checked evidence 不得被编辑成批准。 20. M3-29 只允许记录 M3-28 上游 fingerprint 和 archive/extracted comparison;`comparison_observed=true`、`derivation_provenance_complete=false`,operator/tool/command、modified/additional manifest 和 archive-to-working-set attestation 缺失时,不形成 Landing object、ResourceVersion、PlatformRun、scheduler submission、provider mutation、content admission 或 production ingestion 权威;provenance evidence 不得被 rehash 成批准。 +21. M3-30 只允许选择 `bishan_land_use_dltb_local` 作为首条候选并记录 owner、license、retention、access、privacy/sensitivity、标准版本、DataSLO、golden result 八项 pending decisions;`candidate_scope_selected=true` 不等于 `source_governance_approved`,checked governance evidence 不形成 Landing object、ResourceVersion、PlatformRun、scheduler submission、provider mutation、content admission 或 production ingestion 权威,也不得被 rehash 成批准。 ## 已建立的 AR-0/AR-1 entry 证据 @@ -97,6 +98,7 @@ - Metadata Fabric M3-7 已建立 production identity profile/attestation gate;checked-in profile fingerprint 为 `2e9d5cac3560b853820f923669f6794ead63bcb36a528639fc0e9539e148ee2f`,report fingerprint 为 `c607589ee25a87acc8a1ab71372618a9a4c10c1e8ebff15b8db7e78b37600b9f`,`profile_valid=true`,40 项 federation/provider/TLS/catalog/tenancy/operations 外部输入以 blockers 暴露,`ready_for_protected_verification=false`、`production_identity_gate_passed=false`、`production_ready=false`。合成完整 attestation 只验证门禁逻辑,不计入生产证据。 - AR-2 M3-28 已完成重庆真实源 metadata-only admission baseline:evidence SHA 为 `a2196495d845d61be939c7fc36a7f05c3567e365599d2d04be0aab9c568459c1`,evidence file SHA 为 `9b5c20369c235f7e0a2f2cb0a21cee77f86981aa273bac196605a4803b05ce83`;archive SHA 为 `2043b60c2f4f7f32a31388a634fae4ac28534990e205aa86b8df0e4b64dcbbca`,extracted payload SHA 为 `e7e81e4f53f9f174792f500fbfdfde6bee30ec03beac8cbd91771fe09f548ea6`;11 个 source groups、16 个 asset profiles、57 个 admission blockers、584 个 extracted files,archive/extracted comparison 为 526 exact、6 modified、0 missing、52 additional;`source_content_admitted=false`。该证据不含 source payload、绝对路径、记录值或 geometry,也不证明 Landing authority、ResourceVersion、Run、授权 Artifact、scheduler/provider mutation、生产 ingestion 或 `production_ready`。 - AR-2 M3-29 已建立重庆 extraction provenance gap baseline:evidence SHA 为 `b56ce0c036827d4338ab2cfae8f3fb4c9e1e78ec18aac243272f1f77801300ef`,evidence file SHA 为 `cfae0478c76452a155e8af42ec8499e4e7876a49c1dbb98648526025cb154360`;上游 M3-28 evidence/file SHA 分别为 `a2196495d845d61be939c7fc36a7f05c3567e365599d2d04be0aab9c568459c1` / `9b5c20369c235f7e0a2f2cb0a21cee77f86981aa273bac196605a4803b05ce83`;比较固定为 526 exact、6 modified、0 missing、52 additional,6 项 derivation evidence 缺失,`comparison_observed=true`、`derivation_provenance_complete=false`、`source_content_admitted=false`、`production_ready=false`。该证据只记录比较关系,不含 source payload、绝对路径、记录值或 geometry,也不证明 archive-to-working-set attestation、Landing authority、ResourceVersion、Run、scheduler/provider mutation 或生产 ingestion。 +- AR-2 M3-30 已建立首条重庆地类图斑 source governance gate:候选为 `bishan_land_use_dltb_local`,evidence SHA 为 `97cf11ab8938c048dce9db903d1a4f30758f208dec6dad1a08b740a4a8fe7b6f`,evidence file SHA 为 `25bc5e2dfc5528f5556e7174f8c99fed7abaf30b9312528f5164c16bdf7cca9a`;证据绑定 M3-28/M3-29 fingerprints,八项治理 decision records 全部为 `pending`,另保留 derivation 与 fresh protected attestation blockers;`candidate_scope_selected=true`、`source_governance_approved=false`、`source_content_admitted=false`、`production_ready=false`。该证据不含 source payload、绝对路径、记录值或 geometry,也不证明任何治理批准、Landing authority、ResourceVersion、Run、scheduler/provider mutation 或生产 ingestion。 ## 下一验收证据 @@ -105,5 +107,5 @@ - 为受保护 activation 提供真实 ConfigMap snapshot、Secret key attestation、provider identity 和 reviewer approval,随后完成 managed outbox worker/provider callback 单副本实际部署、唯一 worker ID、status/lease 故障恢复和无双写证据; - 首条真实图斑链对 golden slice 的 output hash、独立质量结果/evidence、血缘、发布 revision 和 rollback 演练; - OpenMetadata/Gravitino 的 source host/cluster 外生产 backup account/bucket、KMS/TLS/workload identity、PITR/source-loss recovery、RPO/RTO、OIDC、受保护环境 provider NetworkPolicy/tenant isolation、upgrade/rollback、registry provenance、持续 metrics backend/retention/query、真实 alert delivery/SLO owner/runbook,以及受保护 PolicyDecision/Approval、双 provider 最小权限 ingestion、生产持久 binding、受保护 production OpenLineage receiver、无双写 read-back 和 conformance;M1 fixture、M2 本地 evidence/readiness contracts、M3-1 projection candidate、M3-2 local replay、M3-3 临时 binding ledger、M3-4 loopback delivery、M3-5/M3-6 本地临时 provider identity 与 M3-7 pending profile/合成 attestation 均不计入生产退出门; -- M3-29 的下一证据必须补齐 operator/tool/command、modified/additional manifest 和 archive-to-working-set attestation,并将 owner/license、retention、access、privacy/sensitivity、标准版本、SLO 和 golden result 决策纳入正式 authority;metadata-only admission/provenance evidence 是不可变检查结果,不能被编辑或重解释为内容批准、Landing authority 或 ingestion authorization; +- M3-29/M3-30 的下一证据必须补齐 operator/tool/command、modified/additional manifest、archive-to-working-set attestation、owner/license/retention/access/privacy-sensitivity/standard-version/DataSLO/golden-result 八项签名决策与 fresh protected attestation;metadata-only admission/provenance/governance evidence 是不可变检查结果,不能被编辑或重解释为内容批准、Landing authority 或 ingestion authorization; - DolphinScheduler/Temporal sandbox 的独立数据库、备份恢复、身份、版本和升级责任证明;DolphinScheduler standalone/H2 不计入此退出门。 diff --git a/scripts/chongqing-source-governance.sh b/scripts/chongqing-source-governance.sh new file mode 100755 index 00000000..04231704 --- /dev/null +++ b/scripts/chongqing-source-governance.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +common_git_dir="$(git -C "$repo_root" rev-parse --path-format=absolute --git-common-dir 2>/dev/null || true)" +shared_root="" +if [ -n "$common_git_dir" ]; then + shared_root="$(cd "$common_git_dir/.." && pwd)" +fi + +if [ -n "${PYTHON:-}" ]; then + : +elif [ -x "$repo_root/.venv/bin/python" ]; then + PYTHON="$repo_root/.venv/bin/python" +elif [ -n "$shared_root" ] && [ -x "$shared_root/.venv/bin/python" ]; then + PYTHON="$shared_root/.venv/bin/python" +else + PYTHON="python" +fi + +cd "$repo_root" +exec "$PYTHON" -m data_agent.chongqing_source_governance "$@"