From 51afef3ccceb11bafcef7280f3b539ae03a3b68e Mon Sep 17 00:00:00 2001 From: 1wgrumph <41762695+1wgrumph@users.noreply.github.com> Date: Mon, 14 Sep 2026 21:57:52 -0500 Subject: [PATCH] Export bran-dev 5a537b9 public snapshot Last export from the private source before it is retired. This snapshot makes alphazede/bran the development home: PRs welcome, security reports go through GitHub private advisories, and unpublished product contracts for enterprise documents and Google attestation ship. Native SDoc is not included. --- .bran-export.json | 262 ++- .github/ISSUE_TEMPLATE/config.yml | 4 +- .github/workflows/bran-fast.yml | 2 + CODE_OF_CONDUCT.md | 5 +- CONTRIBUTING.md | 20 +- README.md | 7 + SECURITY.md | 12 + assets/tui/raven-provenance.json | 1 - .../negative/active-content.json | 149 ++ .../negative/digest-mismatch.json | 145 ++ .../negative/external-reference.json | 147 ++ .../negative/malformed-structure.json | 3 + .../negative/oversized.json | 145 ++ .../negative/unsafe-asset-path.json | 145 ++ .../negative/unsupported-evidence.json | 144 ++ .../positive/docx-flow.json | 123 ++ .../positive/pdf-fixed-layout.json | 145 ++ .../positive/pptx-presentation.json | 135 ++ .../positive/xlsx-grid.json | 126 ++ .../negative/completeness-overclaim.json | 101 ++ .../negative/conflict-mixed-revision.json | 103 ++ .../negative/dlp-rejected.json | 103 ++ .../incomplete-permission-revision.json | 105 ++ .../negative/network-disabled.json | 96 ++ .../opaque-locator-missing-scope.json | 104 ++ .../negative/quota-location-perimeter.json | 105 ++ .../negative/tenant-escape-secret-action.json | 105 ++ .../positive/agent-search.json | 98 ++ .../google-attestation/positive/bran-git.json | 96 ++ .../google-attestation/positive/bran-okf.json | 96 ++ .../positive/document-ai.json | 91 ++ .../positive/gemini-enterprise.json | 98 ++ .../positive/knowledge-catalog.json | 97 ++ .../google-attestation/recorded/input.pdf | Bin 0 -> 193 bytes .../recorded/process-empty-layout.json | 9 + .../recorded/process-layout.json | 98 ++ .../recorded/process-legacy-only.json | 20 + .../recorded/processor-create.json | 7 + .../recorded/processor-missing-identity.json | 5 + ...ise-document-evidence-envelope.schema.json | 399 +++++ schemas/google-source-attestation.schema.json | 329 ++++ tools/ci/check.sh | 10 + tools/ci/document_ai_smoke_adapter.py | 639 ++++++++ tools/ci/enterprise_contract_check.py | 789 +++++++++ tools/ci/google_attestation_contract_check.py | 1427 +++++++++++++++++ tools/ci/test-budget.json | 55 + 46 files changed, 6871 insertions(+), 34 deletions(-) create mode 100644 SECURITY.md create mode 100644 fixtures/enterprise-documents/negative/active-content.json create mode 100644 fixtures/enterprise-documents/negative/digest-mismatch.json create mode 100644 fixtures/enterprise-documents/negative/external-reference.json create mode 100644 fixtures/enterprise-documents/negative/malformed-structure.json create mode 100644 fixtures/enterprise-documents/negative/oversized.json create mode 100644 fixtures/enterprise-documents/negative/unsafe-asset-path.json create mode 100644 fixtures/enterprise-documents/negative/unsupported-evidence.json create mode 100644 fixtures/enterprise-documents/positive/docx-flow.json create mode 100644 fixtures/enterprise-documents/positive/pdf-fixed-layout.json create mode 100644 fixtures/enterprise-documents/positive/pptx-presentation.json create mode 100644 fixtures/enterprise-documents/positive/xlsx-grid.json create mode 100644 fixtures/google-attestation/negative/completeness-overclaim.json create mode 100644 fixtures/google-attestation/negative/conflict-mixed-revision.json create mode 100644 fixtures/google-attestation/negative/dlp-rejected.json create mode 100644 fixtures/google-attestation/negative/incomplete-permission-revision.json create mode 100644 fixtures/google-attestation/negative/network-disabled.json create mode 100644 fixtures/google-attestation/negative/opaque-locator-missing-scope.json create mode 100644 fixtures/google-attestation/negative/quota-location-perimeter.json create mode 100644 fixtures/google-attestation/negative/tenant-escape-secret-action.json create mode 100644 fixtures/google-attestation/positive/agent-search.json create mode 100644 fixtures/google-attestation/positive/bran-git.json create mode 100644 fixtures/google-attestation/positive/bran-okf.json create mode 100644 fixtures/google-attestation/positive/document-ai.json create mode 100644 fixtures/google-attestation/positive/gemini-enterprise.json create mode 100644 fixtures/google-attestation/positive/knowledge-catalog.json create mode 100644 fixtures/google-attestation/recorded/input.pdf create mode 100644 fixtures/google-attestation/recorded/process-empty-layout.json create mode 100644 fixtures/google-attestation/recorded/process-layout.json create mode 100644 fixtures/google-attestation/recorded/process-legacy-only.json create mode 100644 fixtures/google-attestation/recorded/processor-create.json create mode 100644 fixtures/google-attestation/recorded/processor-missing-identity.json create mode 100644 schemas/enterprise-document-evidence-envelope.schema.json create mode 100644 schemas/google-source-attestation.schema.json create mode 100644 tools/ci/document_ai_smoke_adapter.py create mode 100644 tools/ci/enterprise_contract_check.py create mode 100644 tools/ci/google_attestation_contract_check.py diff --git a/.bran-export.json b/.bran-export.json index ccb6c48..37dec34 100644 --- a/.bran-export.json +++ b/.bran-export.json @@ -1,7 +1,7 @@ { "schema_version": 1, "source_repository": "alphazede/bran-dev", - "source_commit": "3feeba3ab30f0504a601ffbb732f98801e25c5e8", + "source_commit": "5a537b986c4a5e7f273e668bb0da1113bd4f368d", "version": "0.1.0", "public_repository": "alphazede/bran", "files": [ @@ -26,8 +26,8 @@ { "path": ".github/ISSUE_TEMPLATE/config.yml", "mode": "100644", - "bytes": 236, - "sha256": "639faef63a4c4deb8b3cb2ac86fdb888f12b51b06c767425c84db56bd8233558" + "bytes": 224, + "sha256": "652afb31886ef5715564b9629181179a168b058999d8e78af58fbe11f7c539aa" }, { "path": ".github/ISSUE_TEMPLATE/feature_request.yml", @@ -35,11 +35,17 @@ "bytes": 1386, "sha256": "6fe630e14a84502ba6c59a961fb84894388369615de101aa70cf2ea3ebd3c6a0" }, + { + "path": ".github/dependabot.yml", + "mode": "100644", + "bytes": 337, + "sha256": "84522e85dcac509080f0a3917bf5ca8910b4951f00ae4f58b6c6a418d79882a8" + }, { "path": ".github/workflows/bran-fast.yml", "mode": "100644", - "bytes": 696, - "sha256": "75002eee19da62eeb11dbc9c86eb67d6fde08635d3277e41298e887305f92128" + "bytes": 939, + "sha256": "f146075aa3cef6074a73ca259ef8c34c83a4511a8d85be2b2f3bdd9f0ddd7f55" }, { "path": ".github/workflows/release.yml", @@ -56,14 +62,14 @@ { "path": "CODE_OF_CONDUCT.md", "mode": "100644", - "bytes": 1346, - "sha256": "ea1c3b2cc119c76a7ce48d0ae84410eb6de22c6fbbe8a24b3aee08e86cf46ae4" + "bytes": 1437, + "sha256": "8fbdc341d5b199a120f091f94316332ff814cc20fcac29a78b4d1174eacebdb4" }, { "path": "CONTRIBUTING.md", "mode": "100644", - "bytes": 2796, - "sha256": "3b268fa87dcfe4341f725bdb89c9d55e94139687d6e74e20f84ec71f201a7b4b" + "bytes": 2654, + "sha256": "717727ab826fed64fc713b60cdbd242a85e5e08ac0ce239d0ce4e3e59c58add3" }, { "path": "Cargo.lock", @@ -98,8 +104,14 @@ { "path": "README.md", "mode": "100644", - "bytes": 10037, - "sha256": "2e0abb9ad0a78c411b75c72d798688c24110b0c57d426af199b8e376b9e37865" + "bytes": 10328, + "sha256": "c2bd0e526ea050b2a82394f0f333acdf0c7e8eea34c21e179bb7586a0ee0bfe1" + }, + { + "path": "SECURITY.md", + "mode": "100644", + "bytes": 465, + "sha256": "d1ba3d328a2068c99bb51ecbc6d2fd832bf6cbb83007e2bc87978b9c6cfeb924" }, { "path": "assets/brand/bran-repository-raven.png", @@ -128,8 +140,8 @@ { "path": "assets/tui/raven-provenance.json", "mode": "100644", - "bytes": 429, - "sha256": "19da415de0b05bcb19b93296e37aa3963cc4b37b3b2d1cdd8e790320834b9cdd" + "bytes": 367, + "sha256": "d105be9ec0dcec5dd7253b00f0d901b54cf67945f9353133bdd8703f9951728e" }, { "path": "assets/tui/raven-wide.txt", @@ -533,6 +545,192 @@ "bytes": 3277, "sha256": "04b59dde1d2de6e4e6c285cfb83572577e809364694d9470cf8584ef62dacfb5" }, + { + "path": "fixtures/enterprise-documents/negative/active-content.json", + "mode": "100644", + "bytes": 3541, + "sha256": "fd07f8e5086866c615afa95202e661ece73c49f47c06b973d8430a524d2b40c0" + }, + { + "path": "fixtures/enterprise-documents/negative/digest-mismatch.json", + "mode": "100644", + "bytes": 3487, + "sha256": "67ef067560d961d617945219076b2647a73a844a3a4728ecbe3b98951129f1c5" + }, + { + "path": "fixtures/enterprise-documents/negative/external-reference.json", + "mode": "100644", + "bytes": 3522, + "sha256": "0461ec2847fec0327357e663bfcccc75e0f6913e517f213ceacd80ef48a9d934" + }, + { + "path": "fixtures/enterprise-documents/negative/malformed-structure.json", + "mode": "100644", + "bytes": 32, + "sha256": "d685b4dea04da7ca0446e79dbf8056f5497194b73cefa7e2a53416f5693320f1" + }, + { + "path": "fixtures/enterprise-documents/negative/oversized.json", + "mode": "100644", + "bytes": 3491, + "sha256": "3fddd59ece9b8f7cb0f47baebe82e8e0e02e86739b57276c67c916da4115ef2a" + }, + { + "path": "fixtures/enterprise-documents/negative/unsafe-asset-path.json", + "mode": "100644", + "bytes": 3484, + "sha256": "f0ccd1927c476bc5469b8187a7ee32c4b03a888d3c7fc9ac7bd02457c34a02e1" + }, + { + "path": "fixtures/enterprise-documents/negative/unsupported-evidence.json", + "mode": "100644", + "bytes": 3459, + "sha256": "ae3763f48c4706fe6960e2c3ea738851b79c264c54861a36604d3d9b1d630482" + }, + { + "path": "fixtures/enterprise-documents/positive/docx-flow.json", + "mode": "100644", + "bytes": 2998, + "sha256": "e1c53e1a1b24ad7745b316c885b0c50053737b99231b2fa2836e00db78cb6fb1" + }, + { + "path": "fixtures/enterprise-documents/positive/pdf-fixed-layout.json", + "mode": "100644", + "bytes": 3487, + "sha256": "ac11999aa73e886aaf9e56c1a7f9f9604a2e0e788c37b5bf12fc5b5db5533507" + }, + { + "path": "fixtures/enterprise-documents/positive/pptx-presentation.json", + "mode": "100644", + "bytes": 3320, + "sha256": "15d0246ee6ae0a478e51d74e5cf936f8633992f43068dbe0f291d6b9bdb31c63" + }, + { + "path": "fixtures/enterprise-documents/positive/xlsx-grid.json", + "mode": "100644", + "bytes": 2883, + "sha256": "5b3d130c9f094da725ce5750dcea83e860631dc64e2c8a34c207d2a4744bebcb" + }, + { + "path": "fixtures/google-attestation/negative/completeness-overclaim.json", + "mode": "100644", + "bytes": 2589, + "sha256": "0ae366c9089898b55384ff3adfc13499c628f6e23af18b37cd91afb91cf9cfbc" + }, + { + "path": "fixtures/google-attestation/negative/conflict-mixed-revision.json", + "mode": "100644", + "bytes": 2607, + "sha256": "2a29428492ffd65bc118d63ef0a6588a2dd25b288271d745871dd5c0f1ed6708" + }, + { + "path": "fixtures/google-attestation/negative/dlp-rejected.json", + "mode": "100644", + "bytes": 2604, + "sha256": "2f6dffaedff01e530b7ff21f815145f50424a02dfa8829d59da6ba39b48c9da5" + }, + { + "path": "fixtures/google-attestation/negative/incomplete-permission-revision.json", + "mode": "100644", + "bytes": 2665, + "sha256": "597afba533a3aa97d74988fdfa897c153574d42036a29b80139f174856715805" + }, + { + "path": "fixtures/google-attestation/negative/network-disabled.json", + "mode": "100644", + "bytes": 2365, + "sha256": "cb16a8483afad4351076eafe8b16efeaeacb85a7215138d528b3ca33d29058c5" + }, + { + "path": "fixtures/google-attestation/negative/opaque-locator-missing-scope.json", + "mode": "100644", + "bytes": 2592, + "sha256": "4762ae01878691e2f43c51a19d6515c0997c29810df254de9c3545b60d02d317" + }, + { + "path": "fixtures/google-attestation/negative/quota-location-perimeter.json", + "mode": "100644", + "bytes": 2692, + "sha256": "db46e339ae36d9c74b39f7c1ffa545bf12e1d7abb9005c5b4d1f62149bcb87e1" + }, + { + "path": "fixtures/google-attestation/negative/tenant-escape-secret-action.json", + "mode": "100644", + "bytes": 2648, + "sha256": "1ed360ceb86fc51f259fbc3482e64f8d4b7a44ad3e9611a1d82ef1e7c40caed1" + }, + { + "path": "fixtures/google-attestation/positive/agent-search.json", + "mode": "100644", + "bytes": 2509, + "sha256": "4ff64a1bde3280095ecc0409ea95a25eaabe06d4e5f2fb319f4c11b2c1ff0713" + }, + { + "path": "fixtures/google-attestation/positive/bran-git.json", + "mode": "100644", + "bytes": 2492, + "sha256": "2898bd03ee1630fa66bc5a0639e0e8e10c4c9377f4662b91577fcc01351c1fbb" + }, + { + "path": "fixtures/google-attestation/positive/bran-okf.json", + "mode": "100644", + "bytes": 2491, + "sha256": "f551ce05cc1cd58bb1ff7d70ec9d6bb6f1d4a5d2f055423b8c824c40cc0edd73" + }, + { + "path": "fixtures/google-attestation/positive/document-ai.json", + "mode": "100644", + "bytes": 2425, + "sha256": "99844b9916cd5365a1d2cf0df04d50166a3f1ed333e864d0109525909529dfc6" + }, + { + "path": "fixtures/google-attestation/positive/gemini-enterprise.json", + "mode": "100644", + "bytes": 2561, + "sha256": "3643b62c2defc6818e64e8d126921df33f2e81816a7139e0b8e3a949f23fc414" + }, + { + "path": "fixtures/google-attestation/positive/knowledge-catalog.json", + "mode": "100644", + "bytes": 2527, + "sha256": "f250a611321120508d3c4662095c8ac01855dbeefb23b21179da7bd9fab92d28" + }, + { + "path": "fixtures/google-attestation/recorded/input.pdf", + "mode": "100644", + "bytes": 193, + "sha256": "8f7c610a4005afe1fb92e3ba9225cba779a051792515944693d81d5b89bbdbbc" + }, + { + "path": "fixtures/google-attestation/recorded/process-empty-layout.json", + "mode": "100644", + "bytes": 178, + "sha256": "8bf96595744dc9e81b1997d517e88245b177f76ea7b4ec1260f9ccaa761ba690" + }, + { + "path": "fixtures/google-attestation/recorded/process-layout.json", + "mode": "100644", + "bytes": 2741, + "sha256": "8192d03f314d8fe18eb9b0c53c277d2469beaa0c9040943a30bc01086bab155c" + }, + { + "path": "fixtures/google-attestation/recorded/process-legacy-only.json", + "mode": "100644", + "bytes": 381, + "sha256": "aa1d4e48f54f55857f9b9881c9b7a3defe4b6da0a6054ec4f6377594ad168d2d" + }, + { + "path": "fixtures/google-attestation/recorded/processor-create.json", + "mode": "100644", + "bytes": 352, + "sha256": "63c5cd357ad4915eff8a87b9d9937243b5cf69d85b8a91a69095e500d414f1da" + }, + { + "path": "fixtures/google-attestation/recorded/processor-missing-identity.json", + "mode": "100644", + "bytes": 106, + "sha256": "38172978da7b7e13cad35db1acf08a7c9732a7d3ea509913ff34bbbe55d17b3d" + }, { "path": "fixtures/graph/knowledge-graph-v1.json", "mode": "100644", @@ -647,6 +845,18 @@ "bytes": 2370, "sha256": "5b0bb6bbf97ec8e84eebc8a249ff98c71098db096b7bff60536f99261dd8a1f2" }, + { + "path": "schemas/enterprise-document-evidence-envelope.schema.json", + "mode": "100644", + "bytes": 14047, + "sha256": "2e7a0d46cadee0af6d997fb866c57c08e9cf0cdc03661cc7ac0149df496ffba1" + }, + { + "path": "schemas/google-source-attestation.schema.json", + "mode": "100644", + "bytes": 11604, + "sha256": "9fea76902c5046e7ce136e4f26260842db872cf78c8dfb5216add5787e45e24f" + }, { "path": "schemas/graph-query-result.schema.json", "mode": "100644", @@ -716,8 +926,26 @@ { "path": "tools/ci/check.sh", "mode": "100755", - "bytes": 4872, - "sha256": "3dbbcf9e9feeac7e84f9344620fff59810d4cd37f3de7d61456537bbdaf37163" + "bytes": 5140, + "sha256": "a2b2977a0f80511513283ae630a9d36fa53ae75453d7d513d3eb386676b52b05" + }, + { + "path": "tools/ci/document_ai_smoke_adapter.py", + "mode": "100644", + "bytes": 23314, + "sha256": "4b7d0850b85bb370a4cbc31617ec7d1568a784eac3f63ce867f49130f38d24dd" + }, + { + "path": "tools/ci/enterprise_contract_check.py", + "mode": "100644", + "bytes": 29714, + "sha256": "3aa6fa597c6cff22b4c61cfba98bde5ef330b59f75a8d78e7b1c2c0dcc59d288" + }, + { + "path": "tools/ci/google_attestation_contract_check.py", + "mode": "100644", + "bytes": 55110, + "sha256": "17b987112199be07f977663110842adade57a898001afb293e961a7a65d9db1e" }, { "path": "tools/ci/public-policy.yaml", @@ -758,8 +986,8 @@ { "path": "tools/ci/test-budget.json", "mode": "100644", - "bytes": 9552, - "sha256": "866404a337764c0b351e7d7ae54c511691fa4a9638f7b8c6c7cf91d7782e9115" + "bytes": 12444, + "sha256": "a6f6e1f5198b1b8bead4fc74bfb1d6c3618ad19e50536fe062f03a5df9b4c228" }, { "path": "tools/ci/test_budget_check.py", diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 42609ae..9d42d68 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: true contact_links: - name: Security issue - url: https://github.com/alphazede/bran/blob/main/CONTRIBUTING.md#security - about: Don't file security problems publicly. See CONTRIBUTING for how to report them. + url: https://github.com/alphazede/bran/security/advisories/new + about: Don't file security problems publicly. Use a private GitHub security advisory. diff --git a/.github/workflows/bran-fast.yml b/.github/workflows/bran-fast.yml index 46b0ecb..dec1ed1 100644 --- a/.github/workflows/bran-fast.yml +++ b/.github/workflows/bran-fast.yml @@ -4,12 +4,14 @@ on: pull_request: push: branches: [main] + workflow_dispatch: permissions: contents: read jobs: fast: + if: ${{ !(github.event_name == 'push' && github.repository == 'alphazede/bran-dev' && github.event.repository.private == true && github.event.repository.custom_properties.delivery_profile == 'private-owner-direct') }} runs-on: ubuntu-24.04 timeout-minutes: 30 steps: diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 3262dcb..f71e54e 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -29,8 +29,9 @@ any public space where you're speaking for BRAN. ## Reporting -Email 1wgrumph@gmail.com. Reports stay private, and I'll respond as quickly as -I reasonably can. +Report privately through +[GitHub Security Advisories](https://github.com/alphazede/bran/security/advisories/new) +and state that it is a code of conduct report. Do not open a public issue. I'll decide what action to take, up to and including blocking someone from the project. If your behaviour makes the project worse for other people, you'll be diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9f13c21..63bf321 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,18 +4,13 @@ Thanks for taking a look. Bug reports are genuinely useful, and the ranking heuristics are where I've been wrong most often, so that's a good place to push. -## First, a note about this repository +## This repository -This repository is a published snapshot. BRAN is developed somewhere else, and -the code here is exported from there and signed. +This is the canonical BRAN source. Issues and pull requests belong here. -**That means pull requests opened here can't be merged.** Not because they -aren't welcome, but because the next export would overwrite them. Sorry. If you -want to change something, open an issue and we'll work out the shape of it -first. If a change is worth making, I'll carry it upstream and credit you in -the commit. - -Issues, questions, and bug reports are all in the right place here. +Open a branch, send a PR against `main`, and keep `./tools/ci/check.sh --fast` +green. Please don't include anything private in issues or PRs. Repository +paths, source excerpts, and query text often carry more than you'd expect. ## Building and testing @@ -66,8 +61,9 @@ reported separately, on purpose. ## Security -Don't open a public issue for a security problem. Email 1wgrumph@gmail.com -instead and I'll deal with it. +Don't open a public issue for a security problem. Report it privately through +[GitHub Security Advisories](https://github.com/alphazede/bran/security/advisories/new). +See [SECURITY.md](SECURITY.md). ## Licence diff --git a/README.md b/README.md index 9ef1dd1..bf6ff8c 100644 --- a/README.md +++ b/README.md @@ -225,6 +225,13 @@ bran -p --agent --offline --no-session "offline return proof" If a capability is unavailable, BRAN says `unavailable` rather than pretending it worked. Requested and effective capability are always reported separately. +## Development + +This repository is the BRAN source. See [CONTRIBUTING.md](CONTRIBUTING.md) to +build, test, and open a pull request. Report vulnerabilities privately through +[GitHub Security Advisories](https://github.com/alphazede/bran/security/advisories/new); +do not open a public issue. + ## Use it with an agent Giving an agent access is not enough. Without a reminder it reaches for diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..ca69dad --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,12 @@ +# Security + +Do not open a public issue for a vulnerability. + +Report privately through +[GitHub Security Advisories](https://github.com/alphazede/bran/security/advisories/new). + +Include `bran --version`, the command you ran, and the JSON it printed. Redact +repository paths and source excerpts you would not publish. + +For conduct reports, use the same private advisory form and state that it is a +code of conduct report. See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). diff --git a/assets/tui/raven-provenance.json b/assets/tui/raven-provenance.json index 585e08a..33a40ee 100644 --- a/assets/tui/raven-provenance.json +++ b/assets/tui/raven-provenance.json @@ -1,5 +1,4 @@ { - "source_reference_path": "/tmp/codex-clipboard-60aMGz.png", "source_reference_sha256": "763b8d9bd431ece3e83bc0f8ef6544c6472fd214571956965e0bdc589df5286f", "creation_method": "agent-authored character artwork", "converter": "not_used", diff --git a/fixtures/enterprise-documents/negative/active-content.json b/fixtures/enterprise-documents/negative/active-content.json new file mode 100644 index 0000000..5a0b5cb --- /dev/null +++ b/fixtures/enterprise-documents/negative/active-content.json @@ -0,0 +1,149 @@ +{ + "admission": { + "packet": "ineligible", + "query": "ineligible", + "reasons": [ + "active-content" + ], + "status": "rejected" + }, + "anchors": [ + { + "family": "fixed-layout", + "id": "anc:pdf:page-1:heading-1", + "locator": { + "bbox": { + "x0": 72, + "x1": 540, + "y0": 720, + "y1": 748 + }, + "block": 1, + "family": "fixed-layout", + "page": 1 + }, + "role": "heading", + "text": "Synthetic PDF layout brief", + "text_digest": "0eccf5ccb2f7dc05bbef6656ae3a0a34f04c449f3b0c4b15a70de42d3cc7aa44" + }, + { + "family": "fixed-layout", + "id": "anc:pdf:page-1:paragraph-1", + "locator": { + "bbox": { + "x0": 72, + "x1": 540, + "y0": 680, + "y1": 710 + }, + "block": 2, + "family": "fixed-layout", + "page": 1 + }, + "role": "paragraph", + "text": "This fixture is a synthetic normalized layout excerpt.", + "text_digest": "0a97350151e16b94d4ad25704a4b0005c2a09dff16ab46678e0d2d208da8aa6b" + } + ], + "assets": [ + { + "byte_length": 128, + "id": "asset:pdf:cover", + "media_type": "image/png", + "path": "assets/cover.png", + "role": "figure", + "sha256": "6ad05dd80aa7f0c315369d5a01db350e129439131bea7ee090aba2c034be8fe4" + } + ], + "envelope_digest": "12526e5b5e1851e6f84a7f63b648fa78b8a958e167ccf55a96634479b1fc30cb", + "evidence_id": "evd:pdf:synthetic-layout-brief", + "fidelity": { + "bounding_boxes": "approximated", + "figures": "normalized", + "javascript": "unsupported", + "ocr": "approximated", + "reading_order": "approximated", + "tables": "unsupported", + "text": "normalized" + }, + "hazards": { + "active_content": { + "kinds": [ + "macro" + ], + "present": true + }, + "external_references": { + "count": 0, + "present": false + } + }, + "normalized": { + "content": { + "family": "fixed-layout", + "language": "en", + "text": "Synthetic PDF layout brief This fixture is a synthetic normalized layout excerpt." + }, + "digest": "036844ad7c5f538411f3d65d407e408cdfb58ced4592149d767c358fc31a98ca" + }, + "original": { + "byte_length": 4096, + "media_type": "application/pdf", + "sha256": "863392864fb6f736d8492844d61ce61678c38f63bc06db1b45e4283b769912ab" + }, + "parser": { + "attestation": "unavailable", + "identity": "google-document-ai", + "processor": "pretrained-layout-parser-v1.0-2024-06-03", + "version": "1.0" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "receipts": { + "malformed_input": { + "present": false, + "reason": null + }, + "truncation": { + "omitted_anchor_count": 0, + "omitted_bytes": 0, + "reason": null, + "truncated": false + }, + "unavailable": { + "features": [ + "javascript", + "tables" + ], + "parser_attestation": true, + "revision": true + } + }, + "relations": [ + { + "from": "anc:pdf:page-1:paragraph-1", + "kind": "derived-from", + "to": "anc:pdf:page-1:heading-1" + } + ], + "schema_version": "1.0.0", + "source": { + "locator": "synthetic:enterprise-documents/pdf-fixed-layout", + "revision": { + "state": "unavailable", + "value": null + } + } +} diff --git a/fixtures/enterprise-documents/negative/digest-mismatch.json b/fixtures/enterprise-documents/negative/digest-mismatch.json new file mode 100644 index 0000000..7402739 --- /dev/null +++ b/fixtures/enterprise-documents/negative/digest-mismatch.json @@ -0,0 +1,145 @@ +{ + "admission": { + "packet": "eligible", + "query": "eligible", + "reasons": [], + "status": "admitted" + }, + "anchors": [ + { + "family": "fixed-layout", + "id": "anc:pdf:page-1:heading-1", + "locator": { + "bbox": { + "x0": 72, + "x1": 540, + "y0": 720, + "y1": 748 + }, + "block": 1, + "family": "fixed-layout", + "page": 1 + }, + "role": "heading", + "text": "Synthetic PDF layout brief", + "text_digest": "0eccf5ccb2f7dc05bbef6656ae3a0a34f04c449f3b0c4b15a70de42d3cc7aa44" + }, + { + "family": "fixed-layout", + "id": "anc:pdf:page-1:paragraph-1", + "locator": { + "bbox": { + "x0": 72, + "x1": 540, + "y0": 680, + "y1": 710 + }, + "block": 2, + "family": "fixed-layout", + "page": 1 + }, + "role": "paragraph", + "text": "This fixture is a synthetic normalized layout excerpt.", + "text_digest": "0a97350151e16b94d4ad25704a4b0005c2a09dff16ab46678e0d2d208da8aa6b" + } + ], + "assets": [ + { + "byte_length": 128, + "id": "asset:pdf:cover", + "media_type": "image/png", + "path": "assets/cover.png", + "role": "figure", + "sha256": "6ad05dd80aa7f0c315369d5a01db350e129439131bea7ee090aba2c034be8fe4" + } + ], + "envelope_digest": "0000000000000000000000000000000000000000000000000000000000000000", + "evidence_id": "evd:pdf:synthetic-layout-brief", + "fidelity": { + "bounding_boxes": "approximated", + "figures": "normalized", + "javascript": "unsupported", + "ocr": "approximated", + "reading_order": "approximated", + "tables": "unsupported", + "text": "normalized" + }, + "hazards": { + "active_content": { + "kinds": [], + "present": false + }, + "external_references": { + "count": 0, + "present": false + } + }, + "normalized": { + "content": { + "family": "fixed-layout", + "language": "en", + "text": "Synthetic PDF layout brief This fixture is a synthetic normalized layout excerpt." + }, + "digest": "036844ad7c5f538411f3d65d407e408cdfb58ced4592149d767c358fc31a98ca" + }, + "original": { + "byte_length": 4096, + "media_type": "application/pdf", + "sha256": "863392864fb6f736d8492844d61ce61678c38f63bc06db1b45e4283b769912ab" + }, + "parser": { + "attestation": "unavailable", + "identity": "google-document-ai", + "processor": "pretrained-layout-parser-v1.0-2024-06-03", + "version": "1.0" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "receipts": { + "malformed_input": { + "present": false, + "reason": null + }, + "truncation": { + "omitted_anchor_count": 0, + "omitted_bytes": 0, + "reason": null, + "truncated": false + }, + "unavailable": { + "features": [ + "javascript", + "tables" + ], + "parser_attestation": true, + "revision": true + } + }, + "relations": [ + { + "from": "anc:pdf:page-1:paragraph-1", + "kind": "derived-from", + "to": "anc:pdf:page-1:heading-1" + } + ], + "schema_version": "1.0.0", + "source": { + "locator": "synthetic:enterprise-documents/pdf-fixed-layout", + "revision": { + "state": "unavailable", + "value": null + } + } +} diff --git a/fixtures/enterprise-documents/negative/external-reference.json b/fixtures/enterprise-documents/negative/external-reference.json new file mode 100644 index 0000000..de5c529 --- /dev/null +++ b/fixtures/enterprise-documents/negative/external-reference.json @@ -0,0 +1,147 @@ +{ + "admission": { + "packet": "ineligible", + "query": "ineligible", + "reasons": [ + "external-reference" + ], + "status": "rejected" + }, + "anchors": [ + { + "family": "fixed-layout", + "id": "anc:pdf:page-1:heading-1", + "locator": { + "bbox": { + "x0": 72, + "x1": 540, + "y0": 720, + "y1": 748 + }, + "block": 1, + "family": "fixed-layout", + "page": 1 + }, + "role": "heading", + "text": "Synthetic PDF layout brief", + "text_digest": "0eccf5ccb2f7dc05bbef6656ae3a0a34f04c449f3b0c4b15a70de42d3cc7aa44" + }, + { + "family": "fixed-layout", + "id": "anc:pdf:page-1:paragraph-1", + "locator": { + "bbox": { + "x0": 72, + "x1": 540, + "y0": 680, + "y1": 710 + }, + "block": 2, + "family": "fixed-layout", + "page": 1 + }, + "role": "paragraph", + "text": "This fixture is a synthetic normalized layout excerpt.", + "text_digest": "0a97350151e16b94d4ad25704a4b0005c2a09dff16ab46678e0d2d208da8aa6b" + } + ], + "assets": [ + { + "byte_length": 128, + "id": "asset:pdf:cover", + "media_type": "image/png", + "path": "assets/cover.png", + "role": "figure", + "sha256": "6ad05dd80aa7f0c315369d5a01db350e129439131bea7ee090aba2c034be8fe4" + } + ], + "envelope_digest": "6d5d66d6a5b5005c37ba4bceac400043dcdbe76170009ae5dbae0c4a15de47f2", + "evidence_id": "evd:pdf:synthetic-layout-brief", + "fidelity": { + "bounding_boxes": "approximated", + "figures": "normalized", + "javascript": "unsupported", + "ocr": "approximated", + "reading_order": "approximated", + "tables": "unsupported", + "text": "normalized" + }, + "hazards": { + "active_content": { + "kinds": [], + "present": false + }, + "external_references": { + "count": 1, + "present": true + } + }, + "normalized": { + "content": { + "family": "fixed-layout", + "language": "en", + "text": "Synthetic PDF layout brief This fixture is a synthetic normalized layout excerpt." + }, + "digest": "036844ad7c5f538411f3d65d407e408cdfb58ced4592149d767c358fc31a98ca" + }, + "original": { + "byte_length": 4096, + "media_type": "application/pdf", + "sha256": "863392864fb6f736d8492844d61ce61678c38f63bc06db1b45e4283b769912ab" + }, + "parser": { + "attestation": "unavailable", + "identity": "google-document-ai", + "processor": "pretrained-layout-parser-v1.0-2024-06-03", + "version": "1.0" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "receipts": { + "malformed_input": { + "present": false, + "reason": null + }, + "truncation": { + "omitted_anchor_count": 0, + "omitted_bytes": 0, + "reason": null, + "truncated": false + }, + "unavailable": { + "features": [ + "javascript", + "tables" + ], + "parser_attestation": true, + "revision": true + } + }, + "relations": [ + { + "from": "anc:pdf:page-1:paragraph-1", + "kind": "derived-from", + "to": "anc:pdf:page-1:heading-1" + } + ], + "schema_version": "1.0.0", + "source": { + "locator": "synthetic:enterprise-documents/pdf-fixed-layout", + "revision": { + "state": "unavailable", + "value": null + } + } +} diff --git a/fixtures/enterprise-documents/negative/malformed-structure.json b/fixtures/enterprise-documents/negative/malformed-structure.json new file mode 100644 index 0000000..46dbf9b --- /dev/null +++ b/fixtures/enterprise-documents/negative/malformed-structure.json @@ -0,0 +1,3 @@ +{ + "schema_version": "1.0.0" +} diff --git a/fixtures/enterprise-documents/negative/oversized.json b/fixtures/enterprise-documents/negative/oversized.json new file mode 100644 index 0000000..c6eb804 --- /dev/null +++ b/fixtures/enterprise-documents/negative/oversized.json @@ -0,0 +1,145 @@ +{ + "admission": { + "packet": "eligible", + "query": "eligible", + "reasons": [], + "status": "admitted" + }, + "anchors": [ + { + "family": "fixed-layout", + "id": "anc:pdf:page-1:heading-1", + "locator": { + "bbox": { + "x0": 72, + "x1": 540, + "y0": 720, + "y1": 748 + }, + "block": 1, + "family": "fixed-layout", + "page": 1 + }, + "role": "heading", + "text": "Synthetic PDF layout brief", + "text_digest": "0eccf5ccb2f7dc05bbef6656ae3a0a34f04c449f3b0c4b15a70de42d3cc7aa44" + }, + { + "family": "fixed-layout", + "id": "anc:pdf:page-1:paragraph-1", + "locator": { + "bbox": { + "x0": 72, + "x1": 540, + "y0": 680, + "y1": 710 + }, + "block": 2, + "family": "fixed-layout", + "page": 1 + }, + "role": "paragraph", + "text": "This fixture is a synthetic normalized layout excerpt.", + "text_digest": "0a97350151e16b94d4ad25704a4b0005c2a09dff16ab46678e0d2d208da8aa6b" + } + ], + "assets": [ + { + "byte_length": 128, + "id": "asset:pdf:cover", + "media_type": "image/png", + "path": "assets/cover.png", + "role": "figure", + "sha256": "6ad05dd80aa7f0c315369d5a01db350e129439131bea7ee090aba2c034be8fe4" + } + ], + "envelope_digest": "e1af7e7bdb664737340f2ea4ebbbca162e83b88113dec33569b23a517abfedbf", + "evidence_id": "evd:pdf:synthetic-layout-brief", + "fidelity": { + "bounding_boxes": "approximated", + "figures": "normalized", + "javascript": "unsupported", + "ocr": "approximated", + "reading_order": "approximated", + "tables": "unsupported", + "text": "normalized" + }, + "hazards": { + "active_content": { + "kinds": [], + "present": false + }, + "external_references": { + "count": 0, + "present": false + } + }, + "normalized": { + "content": { + "family": "fixed-layout", + "language": "en", + "text": "Synthetic PDF layout brief This fixture is a synthetic normalized layout excerpt." + }, + "digest": "036844ad7c5f538411f3d65d407e408cdfb58ced4592149d767c358fc31a98ca" + }, + "original": { + "byte_length": 20971521, + "media_type": "application/pdf", + "sha256": "863392864fb6f736d8492844d61ce61678c38f63bc06db1b45e4283b769912ab" + }, + "parser": { + "attestation": "unavailable", + "identity": "google-document-ai", + "processor": "pretrained-layout-parser-v1.0-2024-06-03", + "version": "1.0" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "receipts": { + "malformed_input": { + "present": false, + "reason": null + }, + "truncation": { + "omitted_anchor_count": 0, + "omitted_bytes": 0, + "reason": null, + "truncated": false + }, + "unavailable": { + "features": [ + "javascript", + "tables" + ], + "parser_attestation": true, + "revision": true + } + }, + "relations": [ + { + "from": "anc:pdf:page-1:paragraph-1", + "kind": "derived-from", + "to": "anc:pdf:page-1:heading-1" + } + ], + "schema_version": "1.0.0", + "source": { + "locator": "synthetic:enterprise-documents/pdf-fixed-layout", + "revision": { + "state": "unavailable", + "value": null + } + } +} diff --git a/fixtures/enterprise-documents/negative/unsafe-asset-path.json b/fixtures/enterprise-documents/negative/unsafe-asset-path.json new file mode 100644 index 0000000..ad7add3 --- /dev/null +++ b/fixtures/enterprise-documents/negative/unsafe-asset-path.json @@ -0,0 +1,145 @@ +{ + "admission": { + "packet": "eligible", + "query": "eligible", + "reasons": [], + "status": "admitted" + }, + "anchors": [ + { + "family": "fixed-layout", + "id": "anc:pdf:page-1:heading-1", + "locator": { + "bbox": { + "x0": 72, + "x1": 540, + "y0": 720, + "y1": 748 + }, + "block": 1, + "family": "fixed-layout", + "page": 1 + }, + "role": "heading", + "text": "Synthetic PDF layout brief", + "text_digest": "0eccf5ccb2f7dc05bbef6656ae3a0a34f04c449f3b0c4b15a70de42d3cc7aa44" + }, + { + "family": "fixed-layout", + "id": "anc:pdf:page-1:paragraph-1", + "locator": { + "bbox": { + "x0": 72, + "x1": 540, + "y0": 680, + "y1": 710 + }, + "block": 2, + "family": "fixed-layout", + "page": 1 + }, + "role": "paragraph", + "text": "This fixture is a synthetic normalized layout excerpt.", + "text_digest": "0a97350151e16b94d4ad25704a4b0005c2a09dff16ab46678e0d2d208da8aa6b" + } + ], + "assets": [ + { + "byte_length": 128, + "id": "asset:pdf:cover", + "media_type": "image/png", + "path": "../secret.png", + "role": "figure", + "sha256": "6ad05dd80aa7f0c315369d5a01db350e129439131bea7ee090aba2c034be8fe4" + } + ], + "envelope_digest": "c493f070bfdc105ef4a3848bdc933062fd6689ba919caab60f3c86402df7a3f3", + "evidence_id": "evd:pdf:synthetic-layout-brief", + "fidelity": { + "bounding_boxes": "approximated", + "figures": "normalized", + "javascript": "unsupported", + "ocr": "approximated", + "reading_order": "approximated", + "tables": "unsupported", + "text": "normalized" + }, + "hazards": { + "active_content": { + "kinds": [], + "present": false + }, + "external_references": { + "count": 0, + "present": false + } + }, + "normalized": { + "content": { + "family": "fixed-layout", + "language": "en", + "text": "Synthetic PDF layout brief This fixture is a synthetic normalized layout excerpt." + }, + "digest": "036844ad7c5f538411f3d65d407e408cdfb58ced4592149d767c358fc31a98ca" + }, + "original": { + "byte_length": 4096, + "media_type": "application/pdf", + "sha256": "863392864fb6f736d8492844d61ce61678c38f63bc06db1b45e4283b769912ab" + }, + "parser": { + "attestation": "unavailable", + "identity": "google-document-ai", + "processor": "pretrained-layout-parser-v1.0-2024-06-03", + "version": "1.0" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "receipts": { + "malformed_input": { + "present": false, + "reason": null + }, + "truncation": { + "omitted_anchor_count": 0, + "omitted_bytes": 0, + "reason": null, + "truncated": false + }, + "unavailable": { + "features": [ + "javascript", + "tables" + ], + "parser_attestation": true, + "revision": true + } + }, + "relations": [ + { + "from": "anc:pdf:page-1:paragraph-1", + "kind": "derived-from", + "to": "anc:pdf:page-1:heading-1" + } + ], + "schema_version": "1.0.0", + "source": { + "locator": "synthetic:enterprise-documents/pdf-fixed-layout", + "revision": { + "state": "unavailable", + "value": null + } + } +} diff --git a/fixtures/enterprise-documents/negative/unsupported-evidence.json b/fixtures/enterprise-documents/negative/unsupported-evidence.json new file mode 100644 index 0000000..dc789cd --- /dev/null +++ b/fixtures/enterprise-documents/negative/unsupported-evidence.json @@ -0,0 +1,144 @@ +{ + "admission": { + "packet": "eligible", + "query": "eligible", + "reasons": [], + "status": "admitted" + }, + "anchors": [ + { + "family": "fixed-layout", + "id": "anc:pdf:page-1:heading-1", + "locator": { + "bbox": { + "x0": 72, + "x1": 540, + "y0": 720, + "y1": 748 + }, + "block": 1, + "family": "fixed-layout", + "page": 1 + }, + "role": "heading", + "text": "Synthetic PDF layout brief", + "text_digest": "0eccf5ccb2f7dc05bbef6656ae3a0a34f04c449f3b0c4b15a70de42d3cc7aa44" + }, + { + "family": "fixed-layout", + "id": "anc:pdf:page-1:paragraph-1", + "locator": { + "bbox": { + "x0": 72, + "x1": 540, + "y0": 680, + "y1": 710 + }, + "block": 2, + "family": "fixed-layout", + "page": 1 + }, + "role": "paragraph", + "text": "This fixture is a synthetic normalized layout excerpt.", + "text_digest": "0a97350151e16b94d4ad25704a4b0005c2a09dff16ab46678e0d2d208da8aa6b" + } + ], + "assets": [ + { + "byte_length": 128, + "id": "asset:pdf:cover", + "media_type": "image/png", + "path": "assets/cover.png", + "role": "figure", + "sha256": "6ad05dd80aa7f0c315369d5a01db350e129439131bea7ee090aba2c034be8fe4" + } + ], + "envelope_digest": "731002d577bf3decfbf4009ec7413a76fae97abe70840144b92507c6a206ba1b", + "evidence_id": "evd:pdf:synthetic-layout-brief", + "fidelity": { + "bounding_boxes": "approximated", + "figures": "normalized", + "javascript": "exact", + "ocr": "approximated", + "reading_order": "approximated", + "tables": "unsupported", + "text": "normalized" + }, + "hazards": { + "active_content": { + "kinds": [], + "present": false + }, + "external_references": { + "count": 0, + "present": false + } + }, + "normalized": { + "content": { + "family": "fixed-layout", + "language": "en", + "text": "Synthetic PDF layout brief This fixture is a synthetic normalized layout excerpt." + }, + "digest": "036844ad7c5f538411f3d65d407e408cdfb58ced4592149d767c358fc31a98ca" + }, + "original": { + "byte_length": 4096, + "media_type": "application/pdf", + "sha256": "863392864fb6f736d8492844d61ce61678c38f63bc06db1b45e4283b769912ab" + }, + "parser": { + "attestation": "unavailable", + "identity": "google-document-ai", + "processor": "pretrained-layout-parser-v1.0-2024-06-03", + "version": "1.0" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "receipts": { + "malformed_input": { + "present": false, + "reason": null + }, + "truncation": { + "omitted_anchor_count": 0, + "omitted_bytes": 0, + "reason": null, + "truncated": false + }, + "unavailable": { + "features": [ + "tables" + ], + "parser_attestation": true, + "revision": true + } + }, + "relations": [ + { + "from": "anc:pdf:page-1:paragraph-1", + "kind": "derived-from", + "to": "anc:pdf:page-1:heading-1" + } + ], + "schema_version": "1.0.0", + "source": { + "locator": "synthetic:enterprise-documents/pdf-fixed-layout", + "revision": { + "state": "unavailable", + "value": null + } + } +} diff --git a/fixtures/enterprise-documents/positive/docx-flow.json b/fixtures/enterprise-documents/positive/docx-flow.json new file mode 100644 index 0000000..571719d --- /dev/null +++ b/fixtures/enterprise-documents/positive/docx-flow.json @@ -0,0 +1,123 @@ +{ + "admission": { + "packet": "eligible", + "query": "eligible", + "reasons": [], + "status": "admitted" + }, + "anchors": [ + { + "family": "flow", + "id": "anc:docx:intro:heading-1", + "locator": { + "family": "flow", + "ordinal": 1, + "section": "intro" + }, + "role": "heading", + "text": "Synthetic DOCX flow brief", + "text_digest": "4605d414ae48b70c659c3a7b88c0cca75f2e279e9dded3d9a0c74700e2bd9576" + }, + { + "family": "flow", + "id": "anc:docx:intro:paragraph-1", + "locator": { + "family": "flow", + "ordinal": 2, + "section": "intro" + }, + "role": "paragraph", + "text": "This fixture is a synthetic normalized flow excerpt.", + "text_digest": "2b273001276ccac3330e113401ca749f7c59b318a1b36617363f9de49975cc85" + } + ], + "assets": [], + "envelope_digest": "e7b570091c3a1c8386591c3c07b3544952c42056ff5eaef1d6a2c78555d734a3", + "evidence_id": "evd:docx:synthetic-flow-brief", + "fidelity": { + "headers_footers": "approximated", + "headings": "exact", + "lists": "normalized", + "macros": "unsupported", + "paragraphs": "exact", + "tables": "normalized", + "text": "exact" + }, + "hazards": { + "active_content": { + "kinds": [], + "present": false + }, + "external_references": { + "count": 0, + "present": false + } + }, + "normalized": { + "content": { + "family": "flow", + "language": "en", + "text": "Synthetic DOCX flow brief This fixture is a synthetic normalized flow excerpt." + }, + "digest": "71faf1c0673408b416157c9fc5cc2d55a37fc5e535b465112ff5e911dc3c55ed" + }, + "original": { + "byte_length": 3072, + "media_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "sha256": "ba1f6363143f52ecd9e2efa62d649f5dd770332574429c9012da2fae585e402d" + }, + "parser": { + "attestation": "unavailable", + "identity": "google-document-ai", + "processor": "pretrained-layout-parser-v1.0-2024-06-03", + "version": "1.0" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "receipts": { + "malformed_input": { + "present": false, + "reason": null + }, + "truncation": { + "omitted_anchor_count": 0, + "omitted_bytes": 0, + "reason": null, + "truncated": false + }, + "unavailable": { + "features": [ + "macros" + ], + "parser_attestation": true, + "revision": true + } + }, + "relations": [ + { + "from": "anc:docx:intro:paragraph-1", + "kind": "derived-from", + "to": "anc:docx:intro:heading-1" + } + ], + "schema_version": "1.0.0", + "source": { + "locator": "synthetic:enterprise-documents/docx-flow", + "revision": { + "state": "unavailable", + "value": null + } + } +} diff --git a/fixtures/enterprise-documents/positive/pdf-fixed-layout.json b/fixtures/enterprise-documents/positive/pdf-fixed-layout.json new file mode 100644 index 0000000..f3fe479 --- /dev/null +++ b/fixtures/enterprise-documents/positive/pdf-fixed-layout.json @@ -0,0 +1,145 @@ +{ + "admission": { + "packet": "eligible", + "query": "eligible", + "reasons": [], + "status": "admitted" + }, + "anchors": [ + { + "family": "fixed-layout", + "id": "anc:pdf:page-1:heading-1", + "locator": { + "bbox": { + "x0": 72, + "x1": 540, + "y0": 720, + "y1": 748 + }, + "block": 1, + "family": "fixed-layout", + "page": 1 + }, + "role": "heading", + "text": "Synthetic PDF layout brief", + "text_digest": "0eccf5ccb2f7dc05bbef6656ae3a0a34f04c449f3b0c4b15a70de42d3cc7aa44" + }, + { + "family": "fixed-layout", + "id": "anc:pdf:page-1:paragraph-1", + "locator": { + "bbox": { + "x0": 72, + "x1": 540, + "y0": 680, + "y1": 710 + }, + "block": 2, + "family": "fixed-layout", + "page": 1 + }, + "role": "paragraph", + "text": "This fixture is a synthetic normalized layout excerpt.", + "text_digest": "0a97350151e16b94d4ad25704a4b0005c2a09dff16ab46678e0d2d208da8aa6b" + } + ], + "assets": [ + { + "byte_length": 128, + "id": "asset:pdf:cover", + "media_type": "image/png", + "path": "assets/cover.png", + "role": "figure", + "sha256": "6ad05dd80aa7f0c315369d5a01db350e129439131bea7ee090aba2c034be8fe4" + } + ], + "envelope_digest": "95e35bfdba0098ad721582c3d6aa37f158b57e2bb9bca8d74456909cf7a52f60", + "evidence_id": "evd:pdf:synthetic-layout-brief", + "fidelity": { + "bounding_boxes": "approximated", + "figures": "normalized", + "javascript": "unsupported", + "ocr": "approximated", + "reading_order": "approximated", + "tables": "unsupported", + "text": "normalized" + }, + "hazards": { + "active_content": { + "kinds": [], + "present": false + }, + "external_references": { + "count": 0, + "present": false + } + }, + "normalized": { + "content": { + "family": "fixed-layout", + "language": "en", + "text": "Synthetic PDF layout brief This fixture is a synthetic normalized layout excerpt." + }, + "digest": "036844ad7c5f538411f3d65d407e408cdfb58ced4592149d767c358fc31a98ca" + }, + "original": { + "byte_length": 4096, + "media_type": "application/pdf", + "sha256": "863392864fb6f736d8492844d61ce61678c38f63bc06db1b45e4283b769912ab" + }, + "parser": { + "attestation": "unavailable", + "identity": "google-document-ai", + "processor": "pretrained-layout-parser-v1.0-2024-06-03", + "version": "1.0" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "receipts": { + "malformed_input": { + "present": false, + "reason": null + }, + "truncation": { + "omitted_anchor_count": 0, + "omitted_bytes": 0, + "reason": null, + "truncated": false + }, + "unavailable": { + "features": [ + "javascript", + "tables" + ], + "parser_attestation": true, + "revision": true + } + }, + "relations": [ + { + "from": "anc:pdf:page-1:paragraph-1", + "kind": "derived-from", + "to": "anc:pdf:page-1:heading-1" + } + ], + "schema_version": "1.0.0", + "source": { + "locator": "synthetic:enterprise-documents/pdf-fixed-layout", + "revision": { + "state": "unavailable", + "value": null + } + } +} diff --git a/fixtures/enterprise-documents/positive/pptx-presentation.json b/fixtures/enterprise-documents/positive/pptx-presentation.json new file mode 100644 index 0000000..c66c208 --- /dev/null +++ b/fixtures/enterprise-documents/positive/pptx-presentation.json @@ -0,0 +1,135 @@ +{ + "admission": { + "packet": "eligible", + "query": "eligible", + "reasons": [], + "status": "admitted" + }, + "anchors": [ + { + "family": "presentation", + "id": "anc:pptx:slide-1:notes-1", + "locator": { + "family": "presentation", + "shape": 2, + "slide": 1, + "z_index": 1 + }, + "role": "notes", + "text": "Speaker notes stay presentation-family evidence.", + "text_digest": "f671405b8c45e362a585b61460393312d0f8a0a2e8c6c3f33a6e12a2bc9ba050" + }, + { + "family": "presentation", + "id": "anc:pptx:slide-1:title-1", + "locator": { + "family": "presentation", + "shape": 1, + "slide": 1, + "z_index": 0 + }, + "role": "title", + "text": "Synthetic PPTX slide brief", + "text_digest": "3ee5fc442de4df623ab1c8ab0dcb67623ef07c8c43dca52d725f8ca0e071a437" + } + ], + "assets": [ + { + "byte_length": 96, + "id": "asset:pptx:hero", + "media_type": "image/png", + "path": "assets/hero.png", + "role": "figure", + "sha256": "7016666e782a5bfc09882879665af02f34aadfe6ce4024f6e015bf5403d5b4f7" + } + ], + "envelope_digest": "51b6df50314793574a50f454b01af9d8631b2af2df3324e2ad41e97c122159e9", + "evidence_id": "evd:pptx:synthetic-slide-brief", + "fidelity": { + "animations": "unsupported", + "macros": "unsupported", + "shapes": "normalized", + "slides": "exact", + "speaker_notes": "exact", + "text": "normalized", + "z_order": "approximated" + }, + "hazards": { + "active_content": { + "kinds": [], + "present": false + }, + "external_references": { + "count": 0, + "present": false + } + }, + "normalized": { + "content": { + "family": "presentation", + "language": "en", + "text": "Synthetic PPTX slide brief Speaker notes stay presentation-family evidence." + }, + "digest": "ebb5f6b2e681e68eaaae2d129268c82d910927560cbee111475b2429f2ce88ee" + }, + "original": { + "byte_length": 5120, + "media_type": "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "sha256": "0f6c8ac9091893bca211dd52ef534c3b1f12ae444b4e06929f168ab18f188e1f" + }, + "parser": { + "attestation": "unavailable", + "identity": "google-document-ai", + "processor": "pretrained-layout-parser-v1.0-2024-06-03", + "version": "1.0" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "receipts": { + "malformed_input": { + "present": false, + "reason": null + }, + "truncation": { + "omitted_anchor_count": 0, + "omitted_bytes": 0, + "reason": null, + "truncated": false + }, + "unavailable": { + "features": [ + "animations", + "macros" + ], + "parser_attestation": true, + "revision": true + } + }, + "relations": [ + { + "from": "anc:pptx:slide-1:notes-1", + "kind": "cites", + "to": "anc:pptx:slide-1:title-1" + } + ], + "schema_version": "1.0.0", + "source": { + "locator": "synthetic:enterprise-documents/pptx-presentation", + "revision": { + "state": "unavailable", + "value": null + } + } +} diff --git a/fixtures/enterprise-documents/positive/xlsx-grid.json b/fixtures/enterprise-documents/positive/xlsx-grid.json new file mode 100644 index 0000000..4fdd843 --- /dev/null +++ b/fixtures/enterprise-documents/positive/xlsx-grid.json @@ -0,0 +1,126 @@ +{ + "admission": { + "packet": "eligible", + "query": "eligible", + "reasons": [], + "status": "admitted" + }, + "anchors": [ + { + "family": "grid", + "id": "anc:xlsx:metrics:cell-2-1", + "locator": { + "column": 1, + "family": "grid", + "row": 2, + "sheet": "Metrics" + }, + "role": "cell", + "text": "42", + "text_digest": "73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049" + }, + { + "family": "grid", + "id": "anc:xlsx:metrics:header-1-1", + "locator": { + "column": 1, + "family": "grid", + "row": 1, + "sheet": "Metrics" + }, + "role": "header", + "text": "Metric", + "text_digest": "2d275a74912cca2e02e6d41f9cae3a75ceef3ed0ef734eff3710e10eb7112228" + } + ], + "assets": [], + "envelope_digest": "2f912e7d7d7b690b14f22bf676b644008ea8f84b9553f1d80b2824e585287818", + "evidence_id": "evd:xlsx:synthetic-grid-brief", + "fidelity": { + "cells": "exact", + "charts": "unsupported", + "formulas": "unsupported", + "macros": "unsupported", + "sheets": "exact", + "text": "exact" + }, + "hazards": { + "active_content": { + "kinds": [], + "present": false + }, + "external_references": { + "count": 0, + "present": false + } + }, + "normalized": { + "content": { + "family": "grid", + "language": "en", + "text": "Metric 42" + }, + "digest": "7753860c9b6bdd2eb1be980594f3cb78a337abde893382ea2454002abfabdf5d" + }, + "original": { + "byte_length": 2048, + "media_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "sha256": "c892b3194922538d021c4c89ee52b14d2c9a31ee146a442e31676fd1d78b166c" + }, + "parser": { + "attestation": "unavailable", + "identity": "google-document-ai", + "processor": "pretrained-layout-parser-v1.0-2024-06-03", + "version": "1.0" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "receipts": { + "malformed_input": { + "present": false, + "reason": null + }, + "truncation": { + "omitted_anchor_count": 0, + "omitted_bytes": 0, + "reason": null, + "truncated": false + }, + "unavailable": { + "features": [ + "charts", + "formulas", + "macros" + ], + "parser_attestation": true, + "revision": true + } + }, + "relations": [ + { + "from": "anc:xlsx:metrics:cell-2-1", + "kind": "derived-from", + "to": "anc:xlsx:metrics:header-1-1" + } + ], + "schema_version": "1.0.0", + "source": { + "locator": "synthetic:enterprise-documents/xlsx-grid", + "revision": { + "state": "unavailable", + "value": null + } + } +} diff --git a/fixtures/google-attestation/negative/completeness-overclaim.json b/fixtures/google-attestation/negative/completeness-overclaim.json new file mode 100644 index 0000000..efc86fc --- /dev/null +++ b/fixtures/google-attestation/negative/completeness-overclaim.json @@ -0,0 +1,101 @@ +{ + "admission": { + "packet": "ineligible", + "query": "ineligible", + "reasons": [ + "completeness-overclaim" + ], + "status": "rejected" + }, + "attestation_digest": "567e717ab57cd50e0da04f734cef02c630f0ef404c7f3ae59acbb362320b253c", + "attestation_id": "att:google:gemini-enterprise:completeness-overclaim", + "capability": { + "attested": "indexed", + "effective": "indexed", + "requested": "indexed" + }, + "checkpoint": { + "digest": "02322d986a8c97ffc720308bdb72da4c0c1ff90c9014d7c190de55e33ba36377", + "id": "ckpt:synthetic:placeholder", + "revision_value": "rev-indexed-1", + "state": "current" + }, + "cost": { + "quota": "unavailable", + "status": "unavailable" + }, + "failures": [ + "completeness-overclaim" + ], + "output": { + "digest": "6b2d7961a0b0379e968853236e4195b27cda104f61292f95a95dea14ce19e1cb", + "envelope_path": null, + "kind": "search-result", + "normalized": { + "item_count": 1, + "result_ids": [ + "res:synthetic:1" + ] + } + }, + "permission": { + "authorization_proof": true, + "status": "attested" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "product": { + "component": "synthetic-engine", + "identity": "google-gemini-enterprise", + "name": "gemini-enterprise", + "version": "1.0" + }, + "profile": { + "identity": "bran-google-enterprise-v1", + "version": "1.0.0" + }, + "runtime": { + "network": "not-invoked" + }, + "schema_version": "1.0.0", + "source": { + "configured_digest": "3d03f54761d3f9bd5b36155cb651e0394795c407a9115d718fd78c37bfe4553b", + "filter_digest": "77b8437827ff79cfa39b4b50769f1057b0e8aa1307e07218b3e7cff7dd786108", + "filters": { + "access_control": "reader-acl", + "connector": "synthetic-workspace-connector", + "data_store": "synthetic-engine-store" + }, + "locator": "projects/synthetic-project/locations/global/collections/default_collection/engines/synthetic-engine", + "revision": { + "kind": "revision", + "state": "attested", + "value": "rev-indexed-1" + } + }, + "tenancy": { + "account_ref": "acct:opaque:synthetic-001", + "location": "global", + "perimeter": "unavailable", + "project": "synthetic-project", + "tenant": "synthetic-tenant" + }, + "truncation": { + "omitted_bytes": 0, + "omitted_item_count": 0, + "reason": null, + "truncated": false + } +} diff --git a/fixtures/google-attestation/negative/conflict-mixed-revision.json b/fixtures/google-attestation/negative/conflict-mixed-revision.json new file mode 100644 index 0000000..8986635 --- /dev/null +++ b/fixtures/google-attestation/negative/conflict-mixed-revision.json @@ -0,0 +1,103 @@ +{ + "admission": { + "packet": "ineligible", + "query": "ineligible", + "reasons": [ + "conflict", + "mixed-revision" + ], + "status": "rejected" + }, + "attestation_digest": "75ac03eecb437e3bcb3a2daa7ebd9d832606bdddc4fcc2a39bb954d3f79c15ba", + "attestation_id": "att:google:gemini-enterprise:conflict-mixed", + "capability": { + "attested": "indexed", + "effective": "indexed", + "requested": "indexed" + }, + "checkpoint": { + "digest": "f48e1d028cf125c758c480d8963d02d6e9b4b5c67efd3de779fa81aa42ff3dc2", + "id": "ckpt:synthetic:gemini-conflict-1", + "revision_value": "rev-indexed-2", + "state": "conflict" + }, + "cost": { + "quota": "unavailable", + "status": "unavailable" + }, + "failures": [ + "conflict", + "mixed-revision" + ], + "output": { + "digest": "6b2d7961a0b0379e968853236e4195b27cda104f61292f95a95dea14ce19e1cb", + "envelope_path": null, + "kind": "search-result", + "normalized": { + "item_count": 1, + "result_ids": [ + "res:synthetic:1" + ] + } + }, + "permission": { + "authorization_proof": false, + "status": "attested" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "product": { + "component": "synthetic-engine", + "identity": "google-gemini-enterprise", + "name": "gemini-enterprise", + "version": "1.0" + }, + "profile": { + "identity": "bran-google-enterprise-v1", + "version": "1.0.0" + }, + "runtime": { + "network": "not-invoked" + }, + "schema_version": "1.0.0", + "source": { + "configured_digest": "3d03f54761d3f9bd5b36155cb651e0394795c407a9115d718fd78c37bfe4553b", + "filter_digest": "77b8437827ff79cfa39b4b50769f1057b0e8aa1307e07218b3e7cff7dd786108", + "filters": { + "access_control": "reader-acl", + "connector": "synthetic-workspace-connector", + "data_store": "synthetic-engine-store" + }, + "locator": "projects/synthetic-project/locations/global/collections/default_collection/engines/synthetic-engine", + "revision": { + "kind": "revision", + "state": "attested", + "value": "rev-indexed-1" + } + }, + "tenancy": { + "account_ref": "acct:opaque:synthetic-001", + "location": "global", + "perimeter": "unavailable", + "project": "synthetic-project", + "tenant": "synthetic-tenant" + }, + "truncation": { + "omitted_bytes": 0, + "omitted_item_count": 0, + "reason": null, + "truncated": false + } +} diff --git a/fixtures/google-attestation/negative/dlp-rejected.json b/fixtures/google-attestation/negative/dlp-rejected.json new file mode 100644 index 0000000..e66af13 --- /dev/null +++ b/fixtures/google-attestation/negative/dlp-rejected.json @@ -0,0 +1,103 @@ +{ + "admission": { + "packet": "ineligible", + "query": "ineligible", + "reasons": [ + "dlp-findings" + ], + "status": "rejected" + }, + "attestation_digest": "bd1ee13cf1ab1355c5606f2c31ae3f7001077351bb47e30236d951f9f2327356", + "attestation_id": "att:google:gemini-enterprise:dlp-rejected", + "capability": { + "attested": "indexed", + "effective": "indexed", + "requested": "indexed" + }, + "checkpoint": { + "digest": "02322d986a8c97ffc720308bdb72da4c0c1ff90c9014d7c190de55e33ba36377", + "id": "ckpt:synthetic:placeholder", + "revision_value": "rev-indexed-1", + "state": "current" + }, + "cost": { + "quota": "unavailable", + "status": "unavailable" + }, + "failures": [ + "dlp-findings" + ], + "output": { + "digest": "6b2d7961a0b0379e968853236e4195b27cda104f61292f95a95dea14ce19e1cb", + "envelope_path": null, + "kind": "search-result", + "normalized": { + "item_count": 1, + "result_ids": [ + "res:synthetic:1" + ] + } + }, + "permission": { + "authorization_proof": false, + "status": "attested" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [ + "synthetic-account-number" + ], + "status": "findings" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "product": { + "component": "synthetic-engine", + "identity": "google-gemini-enterprise", + "name": "gemini-enterprise", + "version": "1.0" + }, + "profile": { + "identity": "bran-google-enterprise-v1", + "version": "1.0.0" + }, + "runtime": { + "network": "not-invoked" + }, + "schema_version": "1.0.0", + "source": { + "configured_digest": "3d03f54761d3f9bd5b36155cb651e0394795c407a9115d718fd78c37bfe4553b", + "filter_digest": "77b8437827ff79cfa39b4b50769f1057b0e8aa1307e07218b3e7cff7dd786108", + "filters": { + "access_control": "reader-acl", + "connector": "synthetic-workspace-connector", + "data_store": "synthetic-engine-store" + }, + "locator": "projects/synthetic-project/locations/global/collections/default_collection/engines/synthetic-engine", + "revision": { + "kind": "revision", + "state": "attested", + "value": "rev-indexed-1" + } + }, + "tenancy": { + "account_ref": "acct:opaque:synthetic-001", + "location": "global", + "perimeter": "unavailable", + "project": "synthetic-project", + "tenant": "synthetic-tenant" + }, + "truncation": { + "omitted_bytes": 0, + "omitted_item_count": 0, + "reason": null, + "truncated": false + } +} diff --git a/fixtures/google-attestation/negative/incomplete-permission-revision.json b/fixtures/google-attestation/negative/incomplete-permission-revision.json new file mode 100644 index 0000000..cea3efd --- /dev/null +++ b/fixtures/google-attestation/negative/incomplete-permission-revision.json @@ -0,0 +1,105 @@ +{ + "admission": { + "packet": "ineligible", + "query": "ineligible", + "reasons": [ + "history-incomplete", + "permission-unavailable", + "stale" + ], + "status": "rejected" + }, + "attestation_digest": "23b2e9867096885625b8e33e5148064d99e525d6cbda17c91d2b35181de31ad9", + "attestation_id": "att:google:gemini-enterprise:incomplete-permission", + "capability": { + "attested": "indexed", + "effective": "indexed", + "requested": "indexed" + }, + "checkpoint": { + "digest": "73addb5906e6aa7b3bad0e401b80b9e844f93fbf8fdcde06dce4105b7f448ffa", + "id": "ckpt:synthetic:gemini-stale-1", + "revision_value": null, + "state": "stale" + }, + "cost": { + "quota": "unavailable", + "status": "unavailable" + }, + "failures": [ + "history-incomplete", + "permission-unavailable", + "stale" + ], + "output": { + "digest": "6b2d7961a0b0379e968853236e4195b27cda104f61292f95a95dea14ce19e1cb", + "envelope_path": null, + "kind": "search-result", + "normalized": { + "item_count": 1, + "result_ids": [ + "res:synthetic:1" + ] + } + }, + "permission": { + "authorization_proof": false, + "status": "partial" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "product": { + "component": "synthetic-engine", + "identity": "google-gemini-enterprise", + "name": "gemini-enterprise", + "version": "1.0" + }, + "profile": { + "identity": "bran-google-enterprise-v1", + "version": "1.0.0" + }, + "runtime": { + "network": "not-invoked" + }, + "schema_version": "1.0.0", + "source": { + "configured_digest": "3d03f54761d3f9bd5b36155cb651e0394795c407a9115d718fd78c37bfe4553b", + "filter_digest": "77b8437827ff79cfa39b4b50769f1057b0e8aa1307e07218b3e7cff7dd786108", + "filters": { + "access_control": "reader-acl", + "connector": "synthetic-workspace-connector", + "data_store": "synthetic-engine-store" + }, + "locator": "projects/synthetic-project/locations/global/collections/default_collection/engines/synthetic-engine", + "revision": { + "kind": "unavailable", + "state": "unavailable", + "value": null + } + }, + "tenancy": { + "account_ref": "acct:opaque:synthetic-001", + "location": "global", + "perimeter": "unavailable", + "project": "synthetic-project", + "tenant": "synthetic-tenant" + }, + "truncation": { + "omitted_bytes": 128, + "omitted_item_count": 3, + "reason": "item-budget", + "truncated": true + } +} diff --git a/fixtures/google-attestation/negative/network-disabled.json b/fixtures/google-attestation/negative/network-disabled.json new file mode 100644 index 0000000..ecd64d5 --- /dev/null +++ b/fixtures/google-attestation/negative/network-disabled.json @@ -0,0 +1,96 @@ +{ + "admission": { + "packet": "ineligible", + "query": "ineligible", + "reasons": [ + "network-disabled" + ], + "status": "rejected" + }, + "attestation_digest": "a86a047030257cb21edeae9cd5141c8d103d989660dd99e96615e908edc54672", + "attestation_id": "att:google:gemini-enterprise:network-disabled", + "capability": { + "attested": "unavailable", + "effective": "unavailable", + "requested": "indexed" + }, + "checkpoint": { + "digest": null, + "id": "ckpt:synthetic:gemini-offline-1", + "revision_value": null, + "state": "unavailable" + }, + "cost": { + "quota": "unavailable", + "status": "unavailable" + }, + "failures": [ + "network-disabled" + ], + "output": { + "digest": null, + "envelope_path": null, + "kind": "unavailable", + "normalized": null + }, + "permission": { + "authorization_proof": false, + "status": "unavailable" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "product": { + "component": "synthetic-engine", + "identity": "google-gemini-enterprise", + "name": "gemini-enterprise", + "version": "1.0" + }, + "profile": { + "identity": "bran-google-enterprise-v1", + "version": "1.0.0" + }, + "runtime": { + "network": "disabled" + }, + "schema_version": "1.0.0", + "source": { + "configured_digest": "3d03f54761d3f9bd5b36155cb651e0394795c407a9115d718fd78c37bfe4553b", + "filter_digest": "77b8437827ff79cfa39b4b50769f1057b0e8aa1307e07218b3e7cff7dd786108", + "filters": { + "access_control": "reader-acl", + "connector": "synthetic-workspace-connector", + "data_store": "synthetic-engine-store" + }, + "locator": "projects/synthetic-project/locations/global/collections/default_collection/engines/synthetic-engine", + "revision": { + "kind": "unavailable", + "state": "unavailable", + "value": null + } + }, + "tenancy": { + "account_ref": "acct:opaque:synthetic-001", + "location": "global", + "perimeter": "unavailable", + "project": "synthetic-project", + "tenant": "synthetic-tenant" + }, + "truncation": { + "omitted_bytes": 0, + "omitted_item_count": 0, + "reason": null, + "truncated": false + } +} diff --git a/fixtures/google-attestation/negative/opaque-locator-missing-scope.json b/fixtures/google-attestation/negative/opaque-locator-missing-scope.json new file mode 100644 index 0000000..4afad2a --- /dev/null +++ b/fixtures/google-attestation/negative/opaque-locator-missing-scope.json @@ -0,0 +1,104 @@ +{ + "admission": { + "packet": "ineligible", + "query": "ineligible", + "reasons": [ + "location-mismatch", + "tenant-escape" + ], + "status": "rejected" + }, + "attestation_digest": "f4888248224a1dea1ca183ff767374826c5640111e2c2291b4f739ad85ade1d2", + "attestation_id": "att:google:gemini-enterprise:opaque-locator", + "capability": { + "attested": "indexed", + "effective": "indexed", + "requested": "indexed" + }, + "checkpoint": { + "digest": "d781dd2acc9636a283378d4f2d557aa9b802a44acb3d7bc37511458c3d69434a", + "id": "ckpt:synthetic:gemini-opaque-1", + "revision_value": "rev-indexed-1", + "state": "current" + }, + "cost": { + "quota": "unavailable", + "status": "unavailable" + }, + "failures": [ + "location-mismatch", + "tenant-escape" + ], + "output": { + "digest": "8264f81ebc04ca38b249be0add9a46598301bc80904f8bfdda114c6f67711421", + "envelope_path": null, + "kind": "search-result", + "normalized": { + "item_count": 2, + "result_ids": [ + "res:synthetic:engine-1", + "res:synthetic:engine-2" + ] + } + }, + "permission": { + "authorization_proof": false, + "status": "attested" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "product": { + "component": "synthetic-engine", + "identity": "google-gemini-enterprise", + "name": "gemini-enterprise", + "version": "1.0" + }, + "profile": { + "identity": "bran-google-enterprise-v1", + "version": "1.0.0" + }, + "runtime": { + "network": "not-invoked" + }, + "schema_version": "1.0.0", + "source": { + "configured_digest": "cd6e26a7523471d53102859aca35d36fa15517b2924629b5a5ba9ffe84dd8198", + "filter_digest": "77b8437827ff79cfa39b4b50769f1057b0e8aa1307e07218b3e7cff7dd786108", + "filters": { + "access_control": "reader-acl", + "connector": "synthetic-workspace-connector", + "data_store": "synthetic-engine-store" + }, + "locator": "opaque:outside-owner-allowlist", + "revision": { + "kind": "revision", + "state": "attested", + "value": "rev-indexed-1" + } + }, + "tenancy": { + "account_ref": "acct:opaque:synthetic-001", + "location": "global", + "perimeter": "unavailable", + "project": "synthetic-project", + "tenant": "synthetic-tenant" + }, + "truncation": { + "omitted_bytes": 0, + "omitted_item_count": 0, + "reason": null, + "truncated": false + } +} diff --git a/fixtures/google-attestation/negative/quota-location-perimeter.json b/fixtures/google-attestation/negative/quota-location-perimeter.json new file mode 100644 index 0000000..118ead3 --- /dev/null +++ b/fixtures/google-attestation/negative/quota-location-perimeter.json @@ -0,0 +1,105 @@ +{ + "admission": { + "packet": "ineligible", + "query": "ineligible", + "reasons": [ + "location-mismatch", + "perimeter-denied", + "quota-exhausted" + ], + "status": "rejected" + }, + "attestation_digest": "341f4d1264279f2780c35b3ad1b4d31b31a0948527eef663504cf47df258b0d0", + "attestation_id": "att:google:knowledge-catalog:quota-location-perimeter", + "capability": { + "attested": "metadata-only", + "effective": "metadata-only", + "requested": "metadata-only" + }, + "checkpoint": { + "digest": "0836d643a7fd5d5467a7e2b600decfa68445328a8f1daad2bdd1523dd7d3c242", + "id": "ckpt:synthetic:knowledge-catalog-quota-1", + "revision_value": "entry-rev-1", + "state": "current" + }, + "cost": { + "quota": "exhausted", + "status": "recorded" + }, + "failures": [ + "location-mismatch", + "perimeter-denied", + "quota-exhausted" + ], + "output": { + "digest": "031813d8ed8536d8e4ed50f755fc14fc269ded8a55ff80ffc1779dbec2bee7a5", + "envelope_path": null, + "kind": "catalog-entry", + "normalized": { + "item_count": 1, + "result_ids": [ + "entry:synthetic-dataset.synthetic-table" + ] + } + }, + "permission": { + "authorization_proof": false, + "status": "attested" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "product": { + "component": "@bigquery", + "identity": "google-knowledge-catalog", + "name": "knowledge-catalog", + "version": "1.0" + }, + "profile": { + "identity": "bran-google-enterprise-v1", + "version": "1.0.0" + }, + "runtime": { + "network": "not-invoked" + }, + "schema_version": "1.0.0", + "source": { + "configured_digest": "134928ebedc058ee9fe22c156896200674f33a62696b417d7860a2df4ffa8874", + "filter_digest": "efc87ac927e47471ec0870f14a144f724af37afd1ad6199a7f54da960039a61a", + "filters": { + "asset_type": "bigquery-table", + "entry_group": "@bigquery", + "include_lineage": "references-only" + }, + "locator": "projects/synthetic-project/locations/eu/entryGroups/@bigquery/entries/synthetic-dataset.synthetic-table", + "revision": { + "kind": "entry", + "state": "attested", + "value": "entry-rev-1" + } + }, + "tenancy": { + "account_ref": "acct:opaque:synthetic-001", + "location": "us", + "perimeter": "denied", + "project": "synthetic-project", + "tenant": "synthetic-tenant" + }, + "truncation": { + "omitted_bytes": 0, + "omitted_item_count": 0, + "reason": null, + "truncated": false + } +} diff --git a/fixtures/google-attestation/negative/tenant-escape-secret-action.json b/fixtures/google-attestation/negative/tenant-escape-secret-action.json new file mode 100644 index 0000000..cf49f8a --- /dev/null +++ b/fixtures/google-attestation/negative/tenant-escape-secret-action.json @@ -0,0 +1,105 @@ +{ + "admission": { + "packet": "ineligible", + "query": "ineligible", + "reasons": [ + "secret-reflection", + "tenant-escape", + "unauthorized-action" + ], + "status": "rejected" + }, + "attestation_digest": "bbae6bdda61fe6f1cb40969ae86a74cfbf7bda1a2e76e07bed080ce996e4dff2", + "attestation_id": "att:google:agent-search:tenant-escape", + "capability": { + "attested": "federated", + "effective": "federated", + "requested": "federated" + }, + "checkpoint": { + "digest": "2e415225cb0bcb5e6f33eb16d993ffb9a348b625716dea94f190f826c246b7b0", + "id": "ckpt:synthetic:agent-search-escape-1", + "revision_value": null, + "state": "current" + }, + "cost": { + "quota": "unavailable", + "status": "unavailable" + }, + "failures": [ + "secret-reflection", + "tenant-escape", + "unauthorized-action" + ], + "output": { + "digest": "6b2d7961a0b0379e968853236e4195b27cda104f61292f95a95dea14ce19e1cb", + "envelope_path": null, + "kind": "search-result", + "normalized": { + "item_count": 1, + "result_ids": [ + "res:synthetic:1" + ] + } + }, + "permission": { + "authorization_proof": false, + "status": "attested" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "product": { + "component": "drive.files.update", + "identity": "google-agent-search", + "name": "agent-search", + "version": "X-Goog-Signature=synthetic" + }, + "profile": { + "identity": "bran-google-enterprise-v1", + "version": "1.0.0" + }, + "runtime": { + "network": "not-invoked" + }, + "schema_version": "1.0.0", + "source": { + "configured_digest": "fb360c4e8d6a2ec6a8d5d2271fb1798686303d4ac97c8159c2fef7525a27ea04", + "filter_digest": "4de3f857f78988660e288a67a01ba003881410b196176a3e7751c01d5b9e974b", + "filters": { + "access_control": "reader-acl", + "data_store": "synthetic-search-store", + "mode": "federated" + }, + "locator": "projects/other-project/locations/global/collections/default_collection/dataStores/synthetic", + "revision": { + "kind": "unavailable", + "state": "unavailable", + "value": null + } + }, + "tenancy": { + "account_ref": "acct:opaque:synthetic-001", + "location": "global", + "perimeter": "unavailable", + "project": "synthetic-project", + "tenant": "synthetic-tenant" + }, + "truncation": { + "omitted_bytes": 0, + "omitted_item_count": 0, + "reason": null, + "truncated": false + } +} diff --git a/fixtures/google-attestation/positive/agent-search.json b/fixtures/google-attestation/positive/agent-search.json new file mode 100644 index 0000000..11bdfa4 --- /dev/null +++ b/fixtures/google-attestation/positive/agent-search.json @@ -0,0 +1,98 @@ +{ + "admission": { + "packet": "eligible", + "query": "eligible", + "reasons": [], + "status": "admitted" + }, + "attestation_digest": "7edbcf2172111175f7123cd18ffab8c447170753f09f9a9b86c75505757eb7f4", + "attestation_id": "att:google:agent-search:synthetic-store", + "capability": { + "attested": "federated", + "effective": "federated", + "requested": "federated" + }, + "checkpoint": { + "digest": "d6bb12fb757bf153def9e9f2c6662693ace5828865da2969963dc3ebd40284d7", + "id": "ckpt:synthetic:agent-search-1", + "revision_value": null, + "state": "current" + }, + "cost": { + "quota": "unavailable", + "status": "unavailable" + }, + "failures": [], + "output": { + "digest": "c5f390e9dead113a1e5810a153904ff0fbcdacfe03627f2c11d879aa7d3b9b0d", + "envelope_path": null, + "kind": "search-result", + "normalized": { + "item_count": 2, + "result_ids": [ + "res:synthetic:store-1", + "res:synthetic:store-2" + ] + } + }, + "permission": { + "authorization_proof": false, + "status": "attested" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "product": { + "component": "synthetic-data-store", + "identity": "google-agent-search", + "name": "agent-search", + "version": "1.0" + }, + "profile": { + "identity": "bran-google-enterprise-v1", + "version": "1.0.0" + }, + "runtime": { + "network": "not-invoked" + }, + "schema_version": "1.0.0", + "source": { + "configured_digest": "0b4b2d20b8c773d323955f26de1eb3e3a45691a0da9677097b0ab51a63fb91ae", + "filter_digest": "4de3f857f78988660e288a67a01ba003881410b196176a3e7751c01d5b9e974b", + "filters": { + "access_control": "reader-acl", + "data_store": "synthetic-search-store", + "mode": "federated" + }, + "locator": "projects/synthetic-project/locations/global/collections/default_collection/dataStores/synthetic-store", + "revision": { + "kind": "unavailable", + "state": "unavailable", + "value": null + } + }, + "tenancy": { + "account_ref": "acct:opaque:synthetic-001", + "location": "global", + "perimeter": "unavailable", + "project": "synthetic-project", + "tenant": "synthetic-tenant" + }, + "truncation": { + "omitted_bytes": 0, + "omitted_item_count": 0, + "reason": null, + "truncated": false + } +} diff --git a/fixtures/google-attestation/positive/bran-git.json b/fixtures/google-attestation/positive/bran-git.json new file mode 100644 index 0000000..6b2f163 --- /dev/null +++ b/fixtures/google-attestation/positive/bran-git.json @@ -0,0 +1,96 @@ +{ + "admission": { + "packet": "eligible", + "query": "eligible", + "reasons": [], + "status": "admitted" + }, + "attestation_digest": "88c1c46cc4f5cd1dcf1a3edd8955e3a8196c87d640f9c129a92a7c1a4e8bc5fa", + "attestation_id": "att:bran-git:synthetic-repo", + "capability": { + "attested": "git-okf", + "effective": "git-okf", + "requested": "git-okf" + }, + "checkpoint": { + "digest": "19e1c44162bab0353fdcc8a12bc09891021d2e1c30c07a0348e79cc0bd423be7", + "id": "ckpt:synthetic:bran-git-1", + "revision_value": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + "state": "current" + }, + "cost": { + "quota": "unavailable", + "status": "unavailable" + }, + "failures": [], + "output": { + "digest": "82f26c3258648689471c7f39ae5a9b01376ac6c9ede74eb260f6e06f5e9be80c", + "envelope_path": null, + "kind": "git-okf", + "normalized": { + "item_count": 1, + "result_ids": [ + "res:synthetic:git-1" + ] + } + }, + "permission": { + "authorization_proof": false, + "status": "attested" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "product": { + "component": "commit", + "identity": "bran-git", + "name": "bran-git", + "version": "1.0" + }, + "profile": { + "identity": "bran-google-enterprise-v1", + "version": "1.0.0" + }, + "runtime": { + "network": "not-invoked" + }, + "schema_version": "1.0.0", + "source": { + "configured_digest": "3638f78887b86e33481c4688d155709ac692e87ca0bbe318f8d42ccdb644e6cc", + "filter_digest": "8d5fee9b510232728fcf91753d5d09a049a0f9017b6312192a98e17282d792db", + "filters": { + "repository": "synthetic-repo", + "snapshot_kind": "commit" + }, + "locator": "git:repository/synthetic-repo/commit/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + "revision": { + "kind": "revision", + "state": "attested", + "value": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + } + }, + "tenancy": { + "account_ref": "acct:opaque:synthetic-001", + "location": "commit", + "perimeter": "unavailable", + "project": "synthetic-repo", + "tenant": "synthetic-tenant" + }, + "truncation": { + "omitted_bytes": 0, + "omitted_item_count": 0, + "reason": null, + "truncated": false + } +} diff --git a/fixtures/google-attestation/positive/bran-okf.json b/fixtures/google-attestation/positive/bran-okf.json new file mode 100644 index 0000000..68c885c --- /dev/null +++ b/fixtures/google-attestation/positive/bran-okf.json @@ -0,0 +1,96 @@ +{ + "admission": { + "packet": "eligible", + "query": "eligible", + "reasons": [], + "status": "admitted" + }, + "attestation_digest": "88bd2d466c0e1895951842edd0c44c3618867d9ee7fa95333e5a824dc301be5c", + "attestation_id": "att:bran-okf:synthetic-bundle", + "capability": { + "attested": "git-okf", + "effective": "git-okf", + "requested": "git-okf" + }, + "checkpoint": { + "digest": "8d00021688727357ec2969d44526f895d2da2bb562be5d2a8f0e758fdd5b213d", + "id": "ckpt:synthetic:bran-okf-1", + "revision_value": "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210", + "state": "current" + }, + "cost": { + "quota": "unavailable", + "status": "unavailable" + }, + "failures": [], + "output": { + "digest": "bc1b2cf49be49e5dff6115a08169f986c1ddef16814718891397374f3466f5ea", + "envelope_path": null, + "kind": "git-okf", + "normalized": { + "item_count": 1, + "result_ids": [ + "res:synthetic:okf-1" + ] + } + }, + "permission": { + "authorization_proof": false, + "status": "attested" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "product": { + "component": "bundle", + "identity": "bran-okf", + "name": "bran-okf", + "version": "1.0" + }, + "profile": { + "identity": "bran-google-enterprise-v1", + "version": "1.0.0" + }, + "runtime": { + "network": "not-invoked" + }, + "schema_version": "1.0.0", + "source": { + "configured_digest": "fe0fa132f10ca17776574a6436a0a842931b2e07645098b112ded99c82d4c9ee", + "filter_digest": "2db91e2109c5644371aa400131ed60f1425f09dea87f015ccb32cc66c23bdc3f", + "filters": { + "bundle": "synthetic-bundle", + "source": "snapshot" + }, + "locator": "okf:bundle/synthetic-bundle/snapshot/fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210", + "revision": { + "kind": "revision", + "state": "attested", + "value": "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210" + } + }, + "tenancy": { + "account_ref": "acct:opaque:synthetic-001", + "location": "snapshot", + "perimeter": "unavailable", + "project": "synthetic-bundle", + "tenant": "synthetic-tenant" + }, + "truncation": { + "omitted_bytes": 0, + "omitted_item_count": 0, + "reason": null, + "truncated": false + } +} diff --git a/fixtures/google-attestation/positive/document-ai.json b/fixtures/google-attestation/positive/document-ai.json new file mode 100644 index 0000000..24c0cd3 --- /dev/null +++ b/fixtures/google-attestation/positive/document-ai.json @@ -0,0 +1,91 @@ +{ + "admission": { + "packet": "eligible", + "query": "eligible", + "reasons": [], + "status": "admitted" + }, + "attestation_digest": "7d89d834bb7e10a0c2a6ca3daa38f6728a6296371f4a28f2b10909a87e6d5711", + "attestation_id": "att:google:document-ai:synthetic-processor", + "capability": { + "attested": "parsed", + "effective": "parsed", + "requested": "parsed" + }, + "checkpoint": { + "digest": null, + "id": "ckpt:synthetic:document-ai-1", + "revision_value": null, + "state": "unavailable" + }, + "cost": { + "quota": "unavailable", + "status": "unavailable" + }, + "failures": [], + "output": { + "digest": "95e35bfdba0098ad721582c3d6aa37f158b57e2bb9bca8d74456909cf7a52f60", + "envelope_path": "fixtures/enterprise-documents/positive/pdf-fixed-layout.json", + "kind": "enterprise-document-evidence-envelope", + "normalized": null + }, + "permission": { + "authorization_proof": false, + "status": "attested" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "product": { + "component": "pretrained-layout-parser-v1.0-2024-06-03", + "identity": "google-document-ai", + "name": "document-ai", + "version": "1.0" + }, + "profile": { + "identity": "bran-google-enterprise-v1", + "version": "1.0.0" + }, + "runtime": { + "network": "not-invoked" + }, + "schema_version": "1.0.0", + "source": { + "configured_digest": "a98c48e55f8d74651aa8e0d8b02796fc1c970ecab898b259be3c06e90d392f1a", + "filter_digest": "a210d49bbb474e58b3a378f027a4eda391fc2b052509faa9ee0738855c1b6317", + "filters": { + "page_limit": "15", + "processor": "pretrained-layout-parser-v1.0-2024-06-03" + }, + "locator": "projects/synthetic-project/locations/us/processors/synthetic-processor/processorVersions/pretrained-layout-parser-v1.0-2024-06-03", + "revision": { + "kind": "unavailable", + "state": "unavailable", + "value": null + } + }, + "tenancy": { + "account_ref": "acct:opaque:synthetic-001", + "location": "us", + "perimeter": "unavailable", + "project": "synthetic-project", + "tenant": "synthetic-tenant" + }, + "truncation": { + "omitted_bytes": 0, + "omitted_item_count": 0, + "reason": null, + "truncated": false + } +} diff --git a/fixtures/google-attestation/positive/gemini-enterprise.json b/fixtures/google-attestation/positive/gemini-enterprise.json new file mode 100644 index 0000000..6794c6e --- /dev/null +++ b/fixtures/google-attestation/positive/gemini-enterprise.json @@ -0,0 +1,98 @@ +{ + "admission": { + "packet": "eligible", + "query": "eligible", + "reasons": [], + "status": "admitted" + }, + "attestation_digest": "a7873a1d5053326eee8d8f1043cbd6da5a5ad53954d87263b0835656a84d6489", + "attestation_id": "att:google:gemini-enterprise:synthetic-engine", + "capability": { + "attested": "indexed", + "effective": "indexed", + "requested": "indexed" + }, + "checkpoint": { + "digest": "6a1f1085a974cb53e742fbcf4fc03ae6a10e0a7534987de76b0a8757eafd7fc7", + "id": "ckpt:synthetic:gemini-enterprise-1", + "revision_value": "rev-indexed-1", + "state": "current" + }, + "cost": { + "quota": "unavailable", + "status": "unavailable" + }, + "failures": [], + "output": { + "digest": "8264f81ebc04ca38b249be0add9a46598301bc80904f8bfdda114c6f67711421", + "envelope_path": null, + "kind": "search-result", + "normalized": { + "item_count": 2, + "result_ids": [ + "res:synthetic:engine-1", + "res:synthetic:engine-2" + ] + } + }, + "permission": { + "authorization_proof": false, + "status": "attested" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "product": { + "component": "synthetic-engine", + "identity": "google-gemini-enterprise", + "name": "gemini-enterprise", + "version": "1.0" + }, + "profile": { + "identity": "bran-google-enterprise-v1", + "version": "1.0.0" + }, + "runtime": { + "network": "not-invoked" + }, + "schema_version": "1.0.0", + "source": { + "configured_digest": "3d03f54761d3f9bd5b36155cb651e0394795c407a9115d718fd78c37bfe4553b", + "filter_digest": "77b8437827ff79cfa39b4b50769f1057b0e8aa1307e07218b3e7cff7dd786108", + "filters": { + "access_control": "reader-acl", + "connector": "synthetic-workspace-connector", + "data_store": "synthetic-engine-store" + }, + "locator": "projects/synthetic-project/locations/global/collections/default_collection/engines/synthetic-engine", + "revision": { + "kind": "revision", + "state": "attested", + "value": "rev-indexed-1" + } + }, + "tenancy": { + "account_ref": "acct:opaque:synthetic-001", + "location": "global", + "perimeter": "unavailable", + "project": "synthetic-project", + "tenant": "synthetic-tenant" + }, + "truncation": { + "omitted_bytes": 0, + "omitted_item_count": 0, + "reason": null, + "truncated": false + } +} diff --git a/fixtures/google-attestation/positive/knowledge-catalog.json b/fixtures/google-attestation/positive/knowledge-catalog.json new file mode 100644 index 0000000..59256f0 --- /dev/null +++ b/fixtures/google-attestation/positive/knowledge-catalog.json @@ -0,0 +1,97 @@ +{ + "admission": { + "packet": "eligible", + "query": "eligible", + "reasons": [], + "status": "admitted" + }, + "attestation_digest": "c87742687df8e969d487bb2b4ba0334c33db287381ab65256f392ee20febbd73", + "attestation_id": "att:google:knowledge-catalog:synthetic-table", + "capability": { + "attested": "metadata-only", + "effective": "metadata-only", + "requested": "metadata-only" + }, + "checkpoint": { + "digest": "6a922b3630f95cd221c0ee7ebfe96c3325e0363ba3e93f45958c35a82b3b11a1", + "id": "ckpt:synthetic:knowledge-catalog-1", + "revision_value": "entry-rev-1", + "state": "current" + }, + "cost": { + "quota": "unavailable", + "status": "unavailable" + }, + "failures": [], + "output": { + "digest": "031813d8ed8536d8e4ed50f755fc14fc269ded8a55ff80ffc1779dbec2bee7a5", + "envelope_path": null, + "kind": "catalog-entry", + "normalized": { + "item_count": 1, + "result_ids": [ + "entry:synthetic-dataset.synthetic-table" + ] + } + }, + "permission": { + "authorization_proof": false, + "status": "attested" + }, + "policy": { + "classification": { + "status": "evaluated", + "value": "internal" + }, + "dlp": { + "findings": [], + "status": "passed" + }, + "public_boundary": { + "outcome": "reject-export", + "value": "internal" + } + }, + "product": { + "component": "@bigquery", + "identity": "google-knowledge-catalog", + "name": "knowledge-catalog", + "version": "1.0" + }, + "profile": { + "identity": "bran-google-enterprise-v1", + "version": "1.0.0" + }, + "runtime": { + "network": "not-invoked" + }, + "schema_version": "1.0.0", + "source": { + "configured_digest": "cd551de27ba354ea873214379619e3cb92b7b6d083f438be43d2781900fa7ec1", + "filter_digest": "efc87ac927e47471ec0870f14a144f724af37afd1ad6199a7f54da960039a61a", + "filters": { + "asset_type": "bigquery-table", + "entry_group": "@bigquery", + "include_lineage": "references-only" + }, + "locator": "projects/synthetic-project/locations/us/entryGroups/@bigquery/entries/synthetic-dataset.synthetic-table", + "revision": { + "kind": "entry", + "state": "attested", + "value": "entry-rev-1" + } + }, + "tenancy": { + "account_ref": "acct:opaque:synthetic-001", + "location": "us", + "perimeter": "unavailable", + "project": "synthetic-project", + "tenant": "synthetic-tenant" + }, + "truncation": { + "omitted_bytes": 0, + "omitted_item_count": 0, + "reason": null, + "truncated": false + } +} diff --git a/fixtures/google-attestation/recorded/input.pdf b/fixtures/google-attestation/recorded/input.pdf new file mode 100644 index 0000000000000000000000000000000000000000..164abdf1e50929e329a4d3b62658347575c907e2 GIT binary patch literal 193 zcmY!laBxxo=T2bvQDN4-DNi708Ehs-9XfDVSJ3B5_RabvEE&!2hIF bool: + try: + target.resolve().relative_to(base.resolve()) + except ValueError: + return False + return True + + +def clip_text(text: str) -> tuple[str, bool]: + raw = text.encode("utf-8") + if len(raw) <= MAX_TEXT_BYTES: + return text, False + return raw[:MAX_TEXT_BYTES].decode("utf-8", errors="ignore"), True + + +def page_of(block: dict[str, Any], inherited: int) -> int: + span = block.get("pageSpan") + if isinstance(span, dict): + start = span.get("pageStart") + if type(start) is int and start >= 1: + return start + return inherited + + +def bbox_of(block: dict[str, Any]) -> tuple[dict[str, int], bool]: + empty = {"x0": 0, "y0": 0, "x1": 0, "y1": 0} + box = block.get("boundingBox") + if not isinstance(box, dict): + return empty, False + vertices = box.get("normalizedVertices") + if not isinstance(vertices, list): + return empty, False + xs: list[int] = [] + ys: list[int] = [] + for vertex in vertices: + if not isinstance(vertex, dict): + continue + x, y = vertex.get("x"), vertex.get("y") + if isinstance(x, (int, float)) and isinstance(y, (int, float)): + xs.append(min(max(int(round(float(x) * BBOX_SCALE)), 0), BBOX_MAX)) + ys.append(min(max(int(round(float(y) * BBOX_SCALE)), 0), BBOX_MAX)) + if not xs: + return empty, False + return {"x0": min(xs), "y0": min(ys), "x1": max(xs), "y1": max(ys)}, True + + +def collect_texts(blocks: object) -> list[str]: + found: list[str] = [] + if not isinstance(blocks, list): + return found + for block in blocks: + if not isinstance(block, dict): + continue + text_block = block.get("textBlock") + if isinstance(text_block, dict): + text = text_block.get("text") + if isinstance(text, str) and text.strip(): + found.append(text.strip()) + found.extend(collect_texts(text_block.get("blocks"))) + table_block = block.get("tableBlock") + if isinstance(table_block, dict): + table_text = table_block_text(table_block) + if table_text: + found.append(table_text) + return found + + +def table_block_text(table_block: dict[str, Any]) -> str: + parts: list[str] = [] + caption = table_block.get("caption") + if isinstance(caption, str) and caption.strip(): + parts.append(caption.strip()) + for key in ("headerRows", "bodyRows"): + rows = table_block.get(key) + if not isinstance(rows, list): + continue + for row in rows: + if not isinstance(row, dict): + continue + cells = row.get("cells") + if not isinstance(cells, list): + continue + for cell in cells: + if not isinstance(cell, dict): + continue + cell_text = " ".join(collect_texts(cell.get("blocks"))) + if cell_text: + parts.append(cell_text) + return " ".join(parts) + + +def role_of(block_type: object) -> str: + if not isinstance(block_type, str): + return "paragraph" + lowered = block_type.strip().lower() + if lowered.startswith("heading"): + return "heading" + if lowered in {"footer", "header", "list", "title"}: + return lowered + return "paragraph" + + +def derive_locator(processor: object) -> tuple[str, dict[str, str]]: + if not isinstance(processor, dict): + raise AdapterError("processor response is not an object") + name = processor.get("name") + default_version = processor.get("defaultProcessorVersion") + version_locator: str | None = None + processor_locator: str | None = None + if isinstance(default_version, str) and default_version.strip(): + parsed = _parse_template_locator( + default_version, GOOGLE_LOCATOR_TEMPLATE["document-ai"] + ) + if parsed is None: + raise AdapterError("default processor version is not a document-ai locator") + version_locator = default_version + if isinstance(name, str) and name.strip(): + as_version = _parse_template_locator(name, GOOGLE_LOCATOR_TEMPLATE["document-ai"]) + as_processor = _parse_template_locator(name, PROCESSOR_TEMPLATE) + if as_version is not None: + if version_locator is not None and name != version_locator: + raise AdapterError("processor name and default version disagree") + version_locator = name + elif as_processor is not None: + processor_locator = name + else: + raise AdapterError("processor name is not a processor identity") + if version_locator is None: + raise AdapterError("full processor identity and default version are unavailable") + parsed_version = _parse_template_locator( + version_locator, GOOGLE_LOCATOR_TEMPLATE["document-ai"] + ) + if parsed_version is None: + raise AdapterError("processor version locator is not a document-ai locator") + if processor_locator is not None: + parsed_processor = _parse_template_locator(processor_locator, PROCESSOR_TEMPLATE) + if parsed_processor is None: + raise AdapterError("processor identity is incomplete") + prefix = processor_locator + "/processorVersions/" + if not version_locator.startswith(prefix): + raise AdapterError("locator and processor identity disagree") + if ( + parsed_processor["project"] != parsed_version["project"] + or parsed_processor["location"] != parsed_version["location"] + or parsed_processor["processor"] != parsed_version["processor"] + ): + raise AdapterError("locator project or location disagrees") + return version_locator, parsed_version + + +def document_of(process: object) -> dict[str, Any]: + if not isinstance(process, dict): + raise AdapterError("process response is not an object") + document = process.get("document") + if not isinstance(document, dict): + raise AdapterError("process response has no document") + mime_type = document.get("mimeType") + if mime_type is not None and mime_type != "application/pdf": + raise AdapterError("process document mimeType is not application/pdf") + return document + + +def layout_blocks(document: dict[str, Any]) -> list[object]: + layout = document.get("documentLayout") + if not isinstance(layout, dict): + raise AdapterError("documentLayout blocks contain no usable text") + blocks = layout.get("blocks") + if not isinstance(blocks, list): + raise AdapterError("documentLayout blocks contain no usable text") + return blocks + + +class LayoutSink: + def __init__(self) -> None: + self.anchors: list[dict[str, Any]] = [] + self.texts: list[str] = [] + self.omitted_anchors = 0 + self.omitted_bytes = 0 + self.saw_table = False + self.saw_bbox = False + + def add(self, role: str, text: str, page: int, bbox: dict[str, int], saw_bbox: bool) -> None: + clipped, truncated = clip_text(text.strip()) + if not clipped.strip(): + return + if truncated: + self.omitted_bytes += max(len(text.encode("utf-8")) - MAX_TEXT_BYTES, 0) + if len(self.anchors) >= 4096: + self.omitted_anchors += 1 + return + index = len(self.anchors) + 1 + self.anchors.append( + { + "id": f"anc:pdf:{page:04d}:{index:04d}", + "family": "fixed-layout", + "role": role, + "text": clipped, + "text_digest": text_digest(clipped), + "locator": { + "family": "fixed-layout", + "page": page, + "block": index, + "bbox": bbox, + }, + } + ) + self.texts.append(clipped) + if saw_bbox: + self.saw_bbox = True + if role == "table": + self.saw_table = True + + def walk(self, blocks: object, inherited_page: int) -> None: + if not isinstance(blocks, list): + return + for block in blocks: + if not isinstance(block, dict): + continue + page = page_of(block, inherited_page) + bbox, saw_bbox = bbox_of(block) + table_block = block.get("tableBlock") + if isinstance(table_block, dict): + table_text = table_block_text(table_block) + if table_text: + self.add("table", table_text, page, bbox, saw_bbox) + continue + text_block = block.get("textBlock") + if isinstance(text_block, dict): + nested = text_block.get("blocks") + before = len(self.anchors) + if isinstance(nested, list) and nested: + self.walk(nested, page) + if len(self.anchors) == before: + text = text_block.get("text") + if isinstance(text, str) and text.strip(): + self.add(role_of(text_block.get("type")), text, page, bbox, saw_bbox) + + +def load_pdf(path: Path) -> tuple[int, str]: + try: + data = path.read_bytes() + except OSError as error: + raise AdapterError(f"cannot read PDF: {error}") from error + if not data.startswith(b"%PDF"): + raise AdapterError("input is not a PDF") + if not (1 <= len(data) <= MAX_ORIGINAL_BYTES): + raise AdapterError("PDF is empty or exceeds the recorded input bound") + return len(data), hashlib.sha256(data).hexdigest() + + +def write_json(path: Path, value: object) -> None: + try: + fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL) + except FileExistsError as error: + raise AdapterError("refusing to overwrite existing adapter output") from error + with os.fdopen(fd, "wb") as handle: + handle.write(canonical_bytes(value) + b"\n") + + +def refuse_existing_outputs(envelope_path: Path, attestation_path: Path) -> None: + if envelope_path.exists() or attestation_path.exists(): + raise AdapterError("refusing to overwrite existing adapter output") + + +def attestation_truncation(envelope: dict[str, Any]) -> dict[str, Any]: + receipts = envelope.get("receipts") + if not isinstance(receipts, dict): + raise AdapterError("envelope truncation receipt is unavailable") + source = receipts.get("truncation") + if not isinstance(source, dict): + raise AdapterError("envelope truncation receipt is unavailable") + truncated = source.get("truncated") + omitted_bytes = source.get("omitted_bytes") + omitted_items = source.get("omitted_anchor_count") + reason = source.get("reason") + if type(truncated) is not bool: + raise AdapterError("envelope truncation receipt is malformed") + if type(omitted_bytes) is not int or omitted_bytes < 0: + raise AdapterError("envelope truncation receipt is malformed") + if type(omitted_items) is not int or omitted_items < 0: + raise AdapterError("envelope truncation receipt is malformed") + if omitted_bytes > MAX_ATTESTATION_BYTES or omitted_items > MAX_RESULT_IDS: + raise AdapterError( + "envelope truncation cannot be represented on the attestation" + ) + if truncated: + if not isinstance(reason, str) or not reason.strip() or len(reason) > 256: + raise AdapterError( + "envelope truncation cannot be represented on the attestation" + ) + elif reason is not None or omitted_bytes != 0 or omitted_items != 0: + raise AdapterError("envelope truncation receipt is malformed") + return { + "truncated": truncated, + "omitted_bytes": omitted_bytes, + "omitted_item_count": omitted_items, + "reason": reason, + } + + +def build_envelope( + *, + source_locator: str, + processor_version: str, + pdf_len: int, + pdf_digest: str, + sink: LayoutSink, +) -> dict[str, Any]: + joined = " ".join(sink.texts) + text, truncated_text = clip_text(joined) + if not text.strip(): + raise AdapterError("documentLayout blocks contain no usable text") + omitted_bytes = sink.omitted_bytes + if truncated_text: + omitted_bytes += max(len(joined.encode("utf-8")) - MAX_TEXT_BYTES, 0) + truncated = bool(sink.omitted_anchors or omitted_bytes) + content = {"family": "fixed-layout", "language": "en", "text": text} + fidelity = { + "bounding_boxes": "approximated" if sink.saw_bbox else "unsupported", + "figures": "unsupported", + "javascript": "unsupported", + "ocr": "approximated", + "reading_order": "approximated", + "tables": "normalized" if sink.saw_table else "unsupported", + "text": "normalized", + } + unavailable = sorted( + feature for feature, status in fidelity.items() if status == "unsupported" + ) + anchors = sorted(sink.anchors, key=lambda item: item["id"]) + # present=false: none observed in the recorded response, not original-PDF proof. + envelope = { + "schema_version": SCHEMA_VERSION, + "evidence_id": f"evd:document-ai:{processor_version}", + "envelope_digest": "0" * 64, + "original": { + "media_type": "application/pdf", + "byte_length": pdf_len, + "sha256": pdf_digest, + }, + "parser": { + "identity": PARSER_IDENTITY, + "processor": processor_version, + "version": PRODUCT_VERSION, + "attestation": "unavailable", + }, + "source": { + "locator": source_locator, + "revision": {"state": "unavailable", "value": None}, + }, + "normalized": {"digest": normalized_digest(content), "content": content}, + "anchors": anchors, + "assets": [], + "relations": [], + "fidelity": fidelity, + "receipts": { + "truncation": { + "truncated": truncated, + "omitted_bytes": omitted_bytes if truncated else 0, + "omitted_anchor_count": sink.omitted_anchors if truncated else 0, + "reason": "normalized-text-bound" if truncated else None, + }, + "malformed_input": {"present": False, "reason": None}, + "unavailable": { + "features": unavailable, + "revision": True, + "parser_attestation": True, + }, + }, + "hazards": { + "active_content": {"present": False, "kinds": []}, + "external_references": {"present": False, "count": 0}, + }, + "policy": { + "classification": {"status": "unavailable", "value": None}, + "dlp": {"status": "not-evaluated", "findings": []}, + "public_boundary": {"outcome": "unavailable", "value": None}, + }, + "admission": { + "status": "rejected", + "packet": "ineligible", + "query": "ineligible", + "reasons": list(ENVELOPE_INELIGIBLE_REASONS), + }, + } + if not is_id(envelope["evidence_id"]): + raise AdapterError("evidence identity cannot be attested") + envelope["envelope_digest"] = envelope_digest(envelope) + reason = classify(envelope) + if reason is not None: + raise AdapterError(f"envelope cannot be attested: {reason}") + return envelope + + +def build_attestation( + *, + locator: str, + parsed: dict[str, str], + processor_version: str, + account_ref: str, + tenant: str, + envelope: dict[str, Any], + effective: str, + attested: str, +) -> dict[str, Any]: + filters = {"processor": processor_version} + tenancy = { + "tenant": tenant, + "project": parsed["project"], + "location": parsed["location"], + "account_ref": account_ref, + "perimeter": "unavailable", + } + record = { + "schema_version": SCHEMA_VERSION, + "attestation_id": f"att:google:document-ai:{parsed['processor']}", + "attestation_digest": "0" * 64, + "profile": {"identity": PROFILE_IDENTITY, "version": SCHEMA_VERSION}, + "product": { + "name": "document-ai", + "identity": PARSER_IDENTITY, + "component": processor_version, + "version": PRODUCT_VERSION, + }, + "capability": { + "requested": REQUESTED_CAPABILITY, + "effective": effective, + "attested": attested, + }, + "tenancy": tenancy, + "source": { + "locator": locator, + "filters": filters, + "configured_digest": configured_digest(tenancy, locator, filters), + "filter_digest": filter_digest(filters), + "revision": {"state": "unavailable", "kind": "unavailable", "value": None}, + }, + "permission": {"status": "unavailable", "authorization_proof": False}, + "output": { + "kind": ENVELOPE_KIND, + "digest": envelope["envelope_digest"], + "envelope_path": ENVELOPE_NAME, + "normalized": None, + }, + "checkpoint": { + "id": f"ckpt:document-ai:{parsed['processor']}", + "digest": None, + "state": "unavailable", + "revision_value": None, + }, + "truncation": attestation_truncation(envelope), + "cost": {"status": "unavailable", "quota": "unavailable"}, + "runtime": {"network": "not-invoked"}, + "policy": { + "classification": {"status": "unavailable", "value": None}, + "dlp": {"status": "not-evaluated", "findings": []}, + "public_boundary": {"outcome": "unavailable", "value": None}, + }, + "failures": [], + "admission": { + "status": "admitted", + "packet": "eligible", + "query": "eligible", + "reasons": [], + }, + } + if not is_id(record["attestation_id"]) or not is_id(record["checkpoint"]["id"]): + raise AdapterError("attestation identity cannot be attested") + path_error = envelope_path_error(record["output"]["envelope_path"], allow_leaf=True) + if path_error is not None: + raise AdapterError(f"output path is unsafe: {path_error}") + record["failures"] = infer_failures(record) + status, packet, query = admission_eligibility(record["failures"]) + record["admission"] = { + "status": status, + "packet": packet, + "query": query, + "reasons": list(record["failures"]), + } + record["attestation_digest"] = attestation_digest(record) + reason = structural_reason(record, envelope=envelope) + if reason is not None: + raise AdapterError(f"attestation cannot be attested: {reason}") + return record + + +def adapt( + processor: object, + process: object, + pdf_len: int, + pdf_digest: str, + account_ref: str, + tenant: str, +) -> tuple[dict[str, Any], dict[str, Any]]: + if not account_ref.startswith(ACCOUNT_PREFIX) or not is_id(account_ref): + raise AdapterError("account reference is not an opaque acct:opaque identity") + if not isinstance(tenant, str) or not tenant.strip() or len(tenant) > 128: + raise AdapterError("tenant is missing") + locator, parsed = derive_locator(processor) + document = document_of(process) + sink = LayoutSink() + sink.walk(layout_blocks(document), 1) + if not sink.texts: + raise AdapterError("documentLayout blocks contain no usable text") + envelope = build_envelope( + source_locator=f"sha256:{pdf_digest}", + processor_version=parsed["processor_version"], + pdf_len=pdf_len, + pdf_digest=pdf_digest, + sink=sink, + ) + effective = "parsed" if sink.texts else "unavailable" + attested = "parsed" if effective == "parsed" and locator else "unavailable" + attestation = build_attestation( + locator=locator, + parsed=parsed, + processor_version=parsed["processor_version"], + account_ref=account_ref, + tenant=tenant, + envelope=envelope, + effective=effective, + attested=attested, + ) + return envelope, attestation + + +def run(argv: list[str]) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--processor-json", required=True, type=Path) + parser.add_argument("--process-json", required=True, type=Path) + parser.add_argument("--input-pdf", required=True, type=Path) + parser.add_argument("--output-dir", required=True, type=Path) + parser.add_argument("--account-ref", required=True) + parser.add_argument("--tenant", required=True) + args = parser.parse_args(argv) + try: + processor = load_json(args.processor_json) + process = load_json(args.process_json) + pdf_len, pdf_digest = load_pdf(args.input_pdf) + output_dir = args.output_dir.resolve() + output_dir.mkdir(parents=True, exist_ok=True) + envelope_path = (output_dir / ENVELOPE_NAME).resolve() + attestation_path = (output_dir / ATTESTATION_NAME).resolve() + if not contained(output_dir, envelope_path) or not contained( + output_dir, attestation_path + ): + raise AdapterError("output paths escape the output directory") + refuse_existing_outputs(envelope_path, attestation_path) + envelope, attestation = adapt( + processor, + process, + pdf_len, + pdf_digest, + args.account_ref, + args.tenant, + ) + write_json(envelope_path, envelope) + write_json(attestation_path, attestation) + except (AdapterError, ValueError, OSError) as error: + print(f"FAIL document-ai recorded adapter: {error}", file=sys.stderr) + return 1 + print(f"PASS document-ai recorded adapter: {envelope_path} {attestation_path}") + return 0 + + +def main() -> int: + return run(sys.argv[1:]) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/ci/enterprise_contract_check.py b/tools/ci/enterprise_contract_check.py new file mode 100644 index 0000000..490ea65 --- /dev/null +++ b/tools/ci/enterprise_contract_check.py @@ -0,0 +1,789 @@ +#!/usr/bin/env python3 +"""Validate the V1 enterprise-document evidence-envelope contract without third-party packages.""" + +from __future__ import annotations + +import hashlib +import json +import re +import sys +from pathlib import Path +from typing import Any + + +SCHEMA_VERSION = "1.0.0" +SCHEMA_ID = ( + "https://schemas.alphazede.dev/bran/enterprise-document-evidence-envelope/v1/schema.json" +) +SEMANTIC_ORACLE = "tools/ci/enterprise_contract_check.py" +SHA256_PATTERN = re.compile(r"[0-9a-f]{64}\Z") +ID_PATTERN = re.compile(r"^[A-Za-z0-9./:_-]+$") +FEATURE_PATTERN = re.compile(r"^[a-z][a-z0-9_]*$") +LANGUAGE_PATTERN = re.compile(r"^[a-z]{2}(?:-[A-Z]{2})?$") +ASSET_PATH_PATTERN = re.compile(r"^[A-Za-z0-9._-]+(?:/[A-Za-z0-9._-]+)*$") +MEDIA_TYPES = { + "application/pdf": "fixed-layout", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "flow", + "application/vnd.openxmlformats-officedocument.presentationml.presentation": "presentation", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "grid", +} +FAMILIES = frozenset(MEDIA_TYPES.values()) +FIDELITY_VALUES = ("exact", "normalized", "approximated", "unsupported") +NEVER_EXACT_FEATURES = frozenset( + { + "macros", + "formulas", + "javascript", + "animations", + "embedded_objects", + "external_relationships", + "round_trip", + } +) +FAMILY_FEATURES = { + "fixed-layout": ( + "text", + "reading_order", + "bounding_boxes", + "tables", + "figures", + "ocr", + "javascript", + ), + "flow": ( + "text", + "paragraphs", + "headings", + "lists", + "tables", + "headers_footers", + "macros", + ), + "presentation": ( + "text", + "slides", + "shapes", + "speaker_notes", + "z_order", + "macros", + "animations", + ), + "grid": ( + "text", + "sheets", + "cells", + "formulas", + "charts", + "macros", + ), +} +ANCHOR_ROLES = { + "fixed-layout": frozenset( + {"heading", "paragraph", "table", "figure", "header", "footer", "title", "list"} + ), + "flow": frozenset( + {"heading", "paragraph", "table", "list", "header", "footer", "title"} + ), + "presentation": frozenset( + {"heading", "paragraph", "table", "list", "title", "notes", "shape"} + ), + "grid": frozenset({"sheet", "cell", "range", "table", "header", "chart"}), +} +RELATION_KINDS = frozenset({"derived-from", "cites", "extracts", "summarizes"}) +ACYCLIC_RELATIONS = frozenset({"derived-from", "extracts"}) +REQUIRED_KEYS = ( + "schema_version", + "evidence_id", + "envelope_digest", + "original", + "parser", + "source", + "normalized", + "anchors", + "assets", + "relations", + "fidelity", + "receipts", + "hazards", + "policy", + "admission", +) +MAX_ENVELOPE_BYTES = 1_048_576 +MAX_ORIGINAL_BYTES = 20_971_520 +MAX_ANCHORS = 4096 +MAX_ASSETS = 256 +MAX_RELATIONS = 8192 +MAX_TEXT_BYTES = 8192 +MAX_ID_BYTES = 256 +MAX_PATH_BYTES = 256 +MAX_FINDINGS = 4096 +MAX_FINDING_BYTES = 128 +POSITIVE_NAMES = ( + "pdf-fixed-layout.json", + "docx-flow.json", + "pptx-presentation.json", + "xlsx-grid.json", +) +NEGATIVE_REASONS = { + "digest-mismatch.json": "digest-mismatch", + "unsafe-asset-path.json": "unsafe-asset-path", + "active-content.json": "active-content", + "external-reference.json": "external-reference", + "malformed-structure.json": "malformed-structure", + "oversized.json": "oversized", + "unsupported-evidence.json": "unsupported-evidence", +} + + +def bran_root() -> Path: + return Path(__file__).resolve().parents[2] + + +def canonical_bytes(value: object) -> bytes: + return json.dumps( + value, sort_keys=True, separators=(",", ":"), ensure_ascii=False + ).encode("utf-8") + + +def sha256_hex(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +def text_digest(text: str) -> str: + return sha256_hex(text.encode("utf-8")) + + +def envelope_digest(envelope: dict[str, Any]) -> str: + body = {key: value for key, value in envelope.items() if key != "envelope_digest"} + return sha256_hex(canonical_bytes(body)) + + +def normalized_digest(content: object) -> str: + return sha256_hex(canonical_bytes(content)) + + +def is_sha256(value: object) -> bool: + return isinstance(value, str) and SHA256_PATTERN.fullmatch(value) is not None + + +def is_id(value: object) -> bool: + return ( + isinstance(value, str) + and 1 <= len(value) <= MAX_ID_BYTES + and ID_PATTERN.fullmatch(value) is not None + ) + + +def is_nonneg_int(value: object, maximum: int) -> bool: + return type(value) is int and 0 <= value <= maximum + + +def is_pos_int(value: object, maximum: int) -> bool: + return type(value) is int and 1 <= value <= maximum + + +def object_without_duplicates(pairs: list[tuple[str, object]]) -> dict[str, object]: + result: dict[str, object] = {} + for key, value in pairs: + if key in result: + raise ValueError(f"duplicate object key {key!r}") + result[key] = value + return result + + +def load_json(path: Path) -> Any: + try: + return json.loads( + path.read_text(encoding="utf-8"), + object_pairs_hook=object_without_duplicates, + ) + except (OSError, UnicodeDecodeError, json.JSONDecodeError, ValueError) as error: + raise ValueError(f"invalid JSON: {path.as_posix()}: {error}") from error + + +def permute(value: object) -> object: + if isinstance(value, dict): + items = list(value.items()) + items.reverse() + return {key: permute(item) for key, item in items} + if isinstance(value, list): + return [permute(item) for item in value] + return value + + +def keys_of(value: object, expected: set[str]) -> bool: + return isinstance(value, dict) and set(value) == expected + + +def sorted_unique(values: list[str]) -> bool: + return values == sorted(set(values)) + + +def unsafe_asset_path(path: object) -> bool: + if not isinstance(path, str) or not path or len(path) > MAX_PATH_BYTES: + return True + if path.startswith("/") or path.startswith("\\") or "\\" in path or "\x00" in path: + return True + if ASSET_PATH_PATTERN.fullmatch(path) is None: + return True + return any(part in {".", ".."} for part in path.split("/")) + + +def locator_error(family: str, locator: object) -> str | None: + if not isinstance(locator, dict) or locator.get("family") != family: + return "malformed-structure" + if family == "fixed-layout": + if set(locator) != {"family", "page", "block", "bbox"}: + return "malformed-structure" + bbox = locator["bbox"] + if not keys_of(bbox, {"x0", "y0", "x1", "y1"}): + return "malformed-structure" + if not is_pos_int(locator["page"], 1_000_000) or not is_pos_int( + locator["block"], 1_000_000 + ): + return "malformed-structure" + for name in ("x0", "y0", "x1", "y1"): + if not is_nonneg_int(bbox[name], 1_000_000): + return "malformed-structure" + if bbox["x1"] < bbox["x0"] or bbox["y1"] < bbox["y0"]: + return "malformed-structure" + return None + if family == "flow": + if set(locator) != {"family", "section", "ordinal"}: + return "malformed-structure" + if not isinstance(locator["section"], str) or not locator["section"].strip(): + return "malformed-structure" + if len(locator["section"]) > MAX_ID_BYTES: + return "malformed-structure" + if not is_pos_int(locator["ordinal"], 1_000_000): + return "malformed-structure" + return None + if family == "presentation": + if set(locator) != {"family", "slide", "shape", "z_index"}: + return "malformed-structure" + if not is_pos_int(locator["slide"], 1_000_000) or not is_pos_int( + locator["shape"], 1_000_000 + ): + return "malformed-structure" + if not is_nonneg_int(locator["z_index"], 1_000_000): + return "malformed-structure" + return None + if family == "grid": + if set(locator) != {"family", "sheet", "row", "column"}: + return "malformed-structure" + if not isinstance(locator["sheet"], str) or not locator["sheet"].strip(): + return "malformed-structure" + if len(locator["sheet"]) > MAX_ID_BYTES: + return "malformed-structure" + if not is_pos_int(locator["row"], 1_048_576) or not is_pos_int( + locator["column"], 16_384 + ): + return "malformed-structure" + return None + return "unsupported-evidence" + + +def has_cycle(nodes: set[str], edges: list[tuple[str, str]]) -> bool: + outgoing: dict[str, list[str]] = {node: [] for node in nodes} + for start, end in edges: + outgoing.setdefault(start, []).append(end) + outgoing.setdefault(end, []) + visiting: set[str] = set() + visited: set[str] = set() + + def walk(node: str) -> bool: + if node in visiting: + return True + if node in visited: + return False + visiting.add(node) + for nxt in outgoing.get(node, []): + if walk(nxt): + return True + visiting.remove(node) + visited.add(node) + return False + + return any(walk(node) for node in sorted(outgoing)) + + +def classify(value: object) -> str | None: + if not isinstance(value, dict): + return "malformed-structure" + if set(value) != set(REQUIRED_KEYS): + return "malformed-structure" + if value.get("schema_version") != SCHEMA_VERSION: + return "malformed-structure" + if not is_id(value.get("evidence_id")) or not is_sha256(value.get("envelope_digest")): + return "malformed-structure" + + original = value.get("original") + if not keys_of(original, {"media_type", "byte_length", "sha256"}): + return "malformed-structure" + if not isinstance(original["media_type"], str) or not original["media_type"]: + return "malformed-structure" + if type(original["byte_length"]) is not int or original["byte_length"] < 1: + return "malformed-structure" + if not is_sha256(original["sha256"]): + return "malformed-structure" + + parser = value.get("parser") + if not keys_of(parser, {"identity", "processor", "version", "attestation"}): + return "malformed-structure" + for field in ("identity", "processor", "version"): + if not isinstance(parser[field], str) or not parser[field].strip(): + return "malformed-structure" + if len(parser[field]) > 512: + return "malformed-structure" + if parser["attestation"] not in {"attested", "unavailable"}: + return "malformed-structure" + + source = value.get("source") + if not keys_of(source, {"locator", "revision"}): + return "malformed-structure" + if not isinstance(source["locator"], str) or not source["locator"].strip(): + return "malformed-structure" + if len(source["locator"]) > 1024: + return "malformed-structure" + revision = source["revision"] + if not keys_of(revision, {"state", "value"}): + return "malformed-structure" + if revision["state"] == "unavailable": + if revision["value"] is not None: + return "malformed-structure" + elif revision["state"] == "attested": + if not isinstance(revision["value"], str) or not revision["value"].strip(): + return "malformed-structure" + if len(revision["value"]) > 256: + return "malformed-structure" + else: + return "malformed-structure" + + normalized = value.get("normalized") + if not keys_of(normalized, {"digest", "content"}): + return "malformed-structure" + if not is_sha256(normalized["digest"]): + return "malformed-structure" + content = normalized["content"] + if not keys_of(content, {"family", "text", "language"}): + return "malformed-structure" + family = content["family"] + if family not in FAMILIES: + return "unsupported-evidence" + if not isinstance(content["text"], str) or not content["text"].strip(): + return "malformed-structure" + if len(content["text"].encode("utf-8")) > MAX_TEXT_BYTES: + return "oversized" + if not isinstance(content["language"], str) or LANGUAGE_PATTERN.fullmatch( + content["language"] + ) is None: + return "malformed-structure" + + anchors = value.get("anchors") + assets = value.get("assets") + relations = value.get("relations") + if not isinstance(anchors, list) or not isinstance(assets, list) or not isinstance( + relations, list + ): + return "malformed-structure" + if not (1 <= len(anchors) <= MAX_ANCHORS): + return "oversized" if len(anchors) > MAX_ANCHORS else "malformed-structure" + if len(assets) > MAX_ASSETS or len(relations) > MAX_RELATIONS: + return "oversized" + if original["byte_length"] > MAX_ORIGINAL_BYTES: + return "oversized" + if len(canonical_bytes(value)) > MAX_ENVELOPE_BYTES: + return "oversized" + + media_family = MEDIA_TYPES.get(original["media_type"]) + if media_family is None: + return "unsupported-evidence" + if media_family != family: + return "unsupported-evidence" + + seen_ids: set[str] = set() + for anchor in anchors: + if not keys_of(anchor, {"id", "family", "role", "text", "text_digest", "locator"}): + return "malformed-structure" + if not is_id(anchor["id"]) or anchor["id"] in seen_ids: + return "malformed-structure" + seen_ids.add(anchor["id"]) + if anchor["family"] != family: + return "unsupported-evidence" + if anchor["role"] not in ANCHOR_ROLES[family]: + return "malformed-structure" + if not isinstance(anchor["text"], str) or not anchor["text"].strip(): + return "malformed-structure" + if len(anchor["text"].encode("utf-8")) > MAX_TEXT_BYTES: + return "oversized" + if not is_sha256(anchor["text_digest"]): + return "malformed-structure" + loc_error = locator_error(family, anchor["locator"]) + if loc_error is not None: + return loc_error + if [anchor["id"] for anchor in anchors] != sorted(anchor["id"] for anchor in anchors): + return "malformed-structure" + + for asset in assets: + if not keys_of( + asset, {"id", "path", "media_type", "byte_length", "sha256", "role"} + ): + return "malformed-structure" + if not is_id(asset["id"]) or asset["id"] in seen_ids: + return "malformed-structure" + seen_ids.add(asset["id"]) + if unsafe_asset_path(asset["path"]): + return "unsafe-asset-path" + if not isinstance(asset["media_type"], str) or not asset["media_type"].strip(): + return "malformed-structure" + if not is_pos_int(asset["byte_length"], MAX_ENVELOPE_BYTES): + return "oversized" if type(asset["byte_length"]) is int and asset[ + "byte_length" + ] > MAX_ENVELOPE_BYTES else "malformed-structure" + if not is_sha256(asset["sha256"]): + return "malformed-structure" + if not isinstance(asset["role"], str) or not asset["role"].strip(): + return "malformed-structure" + if [asset["id"] for asset in assets] != sorted(asset["id"] for asset in assets): + return "malformed-structure" + + relation_keys: list[tuple[str, str, str]] = [] + graph_edges: list[tuple[str, str]] = [] + for relation in relations: + if not keys_of(relation, {"from", "to", "kind"}): + return "malformed-structure" + if relation["from"] not in seen_ids or relation["to"] not in seen_ids: + return "malformed-structure" + if relation["from"] == relation["to"]: + return "malformed-structure" + if relation["kind"] not in RELATION_KINDS: + return "malformed-structure" + relation_keys.append((relation["from"], relation["to"], relation["kind"])) + if relation["kind"] in ACYCLIC_RELATIONS: + graph_edges.append((relation["from"], relation["to"])) + if relation_keys != sorted(set(relation_keys)): + return "malformed-structure" + if has_cycle(seen_ids, graph_edges): + return "malformed-structure" + + fidelity = value.get("fidelity") + required_features = FAMILY_FEATURES[family] + if not isinstance(fidelity, dict) or set(fidelity) != set(required_features): + return "malformed-structure" + for feature, status in fidelity.items(): + if FEATURE_PATTERN.fullmatch(feature) is None or status not in FIDELITY_VALUES: + return "malformed-structure" + if feature in NEVER_EXACT_FEATURES and status == "exact": + return "unsupported-evidence" + + receipts = value.get("receipts") + if not keys_of(receipts, {"truncation", "malformed_input", "unavailable"}): + return "malformed-structure" + truncation = receipts["truncation"] + if not keys_of( + truncation, {"truncated", "omitted_bytes", "omitted_anchor_count", "reason"} + ): + return "malformed-structure" + if type(truncation["truncated"]) is not bool: + return "malformed-structure" + if not is_nonneg_int(truncation["omitted_bytes"], MAX_ORIGINAL_BYTES): + return "malformed-structure" + if not is_nonneg_int(truncation["omitted_anchor_count"], MAX_ANCHORS): + return "malformed-structure" + if truncation["truncated"]: + if not isinstance(truncation["reason"], str) or not truncation["reason"].strip(): + return "malformed-structure" + elif truncation["reason"] is not None or truncation["omitted_bytes"] != 0 or truncation[ + "omitted_anchor_count" + ] != 0: + return "malformed-structure" + malformed_input = receipts["malformed_input"] + if not keys_of(malformed_input, {"present", "reason"}): + return "malformed-structure" + if type(malformed_input["present"]) is not bool: + return "malformed-structure" + if malformed_input["present"]: + if not isinstance(malformed_input["reason"], str) or not malformed_input[ + "reason" + ].strip(): + return "malformed-structure" + elif malformed_input["reason"] is not None: + return "malformed-structure" + unavailable = receipts["unavailable"] + if not keys_of(unavailable, {"features", "revision", "parser_attestation"}): + return "malformed-structure" + features = unavailable["features"] + if not isinstance(features, list) or not all(isinstance(item, str) for item in features): + return "malformed-structure" + if not sorted_unique(features) or not all( + item in required_features for item in features + ): + return "malformed-structure" + expected_unavailable = sorted( + feature for feature, status in fidelity.items() if status == "unsupported" + ) + if features != expected_unavailable: + return "malformed-structure" + if type(unavailable["revision"]) is not bool or type( + unavailable["parser_attestation"] + ) is not bool: + return "malformed-structure" + if unavailable["revision"] != (revision["state"] == "unavailable"): + return "malformed-structure" + if unavailable["parser_attestation"] != (parser["attestation"] == "unavailable"): + return "malformed-structure" + + hazards = value.get("hazards") + if not keys_of(hazards, {"active_content", "external_references"}): + return "malformed-structure" + active = hazards["active_content"] + if not keys_of(active, {"present", "kinds"}): + return "malformed-structure" + if type(active["present"]) is not bool or not isinstance(active["kinds"], list): + return "malformed-structure" + if not all(isinstance(item, str) and item.strip() and len(item) <= 64 for item in active["kinds"]): + return "malformed-structure" + if not sorted_unique(active["kinds"]): + return "malformed-structure" + if active["present"] != bool(active["kinds"]): + return "malformed-structure" + if active["present"]: + return "active-content" + external = hazards["external_references"] + if not keys_of(external, {"present", "count"}): + return "malformed-structure" + if type(external["present"]) is not bool or not is_nonneg_int(external["count"], 1_000_000): + return "malformed-structure" + if external["present"] != (external["count"] > 0): + return "malformed-structure" + if external["present"]: + return "external-reference" + + policy = value.get("policy") + if not keys_of(policy, {"classification", "dlp", "public_boundary"}): + return "malformed-structure" + classification = policy["classification"] + if not keys_of(classification, {"status", "value"}): + return "malformed-structure" + if classification["status"] == "unavailable": + if classification["value"] is not None: + return "malformed-structure" + elif classification["status"] == "evaluated": + if classification["value"] not in { + "public", + "public-compatible", + "private", + "internal", + }: + return "malformed-structure" + else: + return "malformed-structure" + dlp = policy["dlp"] + if not keys_of(dlp, {"status", "findings"}): + return "malformed-structure" + if dlp["status"] not in {"not-evaluated", "passed", "findings"}: + return "malformed-structure" + findings = dlp["findings"] + if not isinstance(findings, list) or len(findings) > MAX_FINDINGS: + return "malformed-structure" if not isinstance(findings, list) else "oversized" + if not all( + isinstance(item, str) and item.strip() and len(item) <= MAX_FINDING_BYTES + for item in findings + ): + return "malformed-structure" + if not sorted_unique(findings): + return "malformed-structure" + if dlp["status"] == "findings": + if not findings: + return "malformed-structure" + elif findings: + return "malformed-structure" + boundary = policy["public_boundary"] + if not keys_of(boundary, {"outcome", "value"}): + return "malformed-structure" + if boundary["outcome"] not in {"admit-export", "reject-export", "unavailable"}: + return "malformed-structure" + if boundary["outcome"] == "unavailable": + if boundary["value"] is not None: + return "malformed-structure" + elif boundary["value"] not in {"public", "public-compatible", "private", "internal"}: + return "malformed-structure" + if boundary["outcome"] == "admit-export" and boundary["value"] != "public": + return "malformed-structure" + + admission = value.get("admission") + if not keys_of(admission, {"status", "packet", "query", "reasons"}): + return "malformed-structure" + if admission["status"] not in {"admitted", "rejected"}: + return "malformed-structure" + if admission["packet"] not in {"eligible", "ineligible"}: + return "malformed-structure" + if admission["query"] not in {"eligible", "ineligible"}: + return "malformed-structure" + reasons = admission["reasons"] + if not isinstance(reasons, list) or not all(isinstance(item, str) and item.strip() for item in reasons): + return "malformed-structure" + if not sorted_unique(reasons): + return "malformed-structure" + blocked = dlp["status"] == "findings" + if blocked: + if ( + admission["status"] != "rejected" + or admission["packet"] != "ineligible" + or admission["query"] != "ineligible" + or "dlp-findings" not in reasons + ): + return "malformed-structure" + elif admission["status"] == "admitted": + if admission["packet"] != "eligible" or admission["query"] != "eligible" or reasons: + return "malformed-structure" + else: + if admission["packet"] != "ineligible" or admission["query"] != "ineligible" or not reasons: + return "malformed-structure" + + for anchor in anchors: + if anchor["text_digest"] != text_digest(anchor["text"]): + return "digest-mismatch" + if normalized["digest"] != normalized_digest(content): + return "digest-mismatch" + if value["envelope_digest"] != envelope_digest(value): + return "digest-mismatch" + return None + + +def validate_schema(schema: object) -> list[str]: + errors: list[str] = [] + if not isinstance(schema, dict): + return ["schema root must be an object"] + if schema.get("$id") != SCHEMA_ID: + errors.append("schema $id is not the V1 enterprise-document evidence-envelope id") + if schema.get("x-semantic-oracle") != SEMANTIC_ORACLE: + errors.append("schema missing or incorrect x-semantic-oracle annotation") + required = schema.get("required") + if required != list(REQUIRED_KEYS): + errors.append("schema required keys drifted from the semantic oracle") + properties = schema.get("properties") + if not isinstance(properties, dict): + errors.append("schema properties must be an object") + return errors + version = properties.get("schema_version", {}) + if not isinstance(version, dict) or version.get("const") != SCHEMA_VERSION: + errors.append("schema_version const drifted from 1.0.0") + original = properties.get("original", {}) + media = ( + original.get("properties", {}).get("media_type", {}) + if isinstance(original, dict) + else {} + ) + if not isinstance(media, dict) or set(media.get("enum", [])) != set(MEDIA_TYPES): + errors.append("original.media_type enum drifted from the four V1 media types") + fidelity = properties.get("fidelity", {}) + additional = fidelity.get("additionalProperties") if isinstance(fidelity, dict) else None + if not isinstance(additional, dict) or additional.get("enum") != list(FIDELITY_VALUES): + errors.append("fidelity value enum drifted from exact/normalized/approximated/unsupported") + return errors + + +def load_named_json(path: Path) -> tuple[object | None, str | None]: + try: + return load_json(path), None + except ValueError: + return None, "malformed-structure" + + +def main() -> int: + root = bran_root() + schema_path = root / "schemas/enterprise-document-evidence-envelope.schema.json" + fixture_root = root / "fixtures/enterprise-documents" + positive_root = fixture_root / "positive" + negative_root = fixture_root / "negative" + try: + schema = load_json(schema_path) + except ValueError as error: + print(f"FAIL enterprise contract check: {error}") + return 1 + + schema_errors = validate_schema(schema) + if schema_errors: + print("FAIL enterprise contract check: schema drifted") + for error in schema_errors: + print(f" {error}") + return 1 + + missing = [ + path.as_posix() + for path in ( + [positive_root / name for name in POSITIVE_NAMES] + + [negative_root / name for name in NEGATIVE_REASONS] + ) + if not path.is_file() + ] + if missing: + print("FAIL enterprise contract check: missing fixtures") + for path in missing: + print(f" {path}") + return 1 + + failures: list[str] = [] + accepted = 0 + golden_bytes: bytes | None = None + golden_digest: str | None = None + for name in POSITIVE_NAMES: + path = positive_root / name + envelope, parse_reason = load_named_json(path) + reason = parse_reason if parse_reason is not None else classify(envelope) + if reason is not None: + failures.append(f"{path.relative_to(root).as_posix()} rejected as {reason}") + continue + assert isinstance(envelope, dict) + if envelope["admission"]["status"] != "admitted": + failures.append(f"{path.relative_to(root).as_posix()} is structurally valid but not admitted") + continue + accepted += 1 + if golden_bytes is None: + golden_bytes = canonical_bytes(envelope) + golden_digest = envelope["envelope_digest"] + permuted = permute(envelope) + if canonical_bytes(permuted) != golden_bytes: + failures.append("permuted in-memory JSON did not serialize to the golden canonical bytes") + if not isinstance(permuted, dict) or envelope_digest(permuted) != golden_digest: + failures.append("permuted in-memory JSON did not reproduce the golden envelope digest") + + rejected = 0 + for name, expected in NEGATIVE_REASONS.items(): + path = negative_root / name + envelope, parse_reason = load_named_json(path) + reason = parse_reason if parse_reason is not None else classify(envelope) + if reason != expected: + failures.append( + f"{path.relative_to(root).as_posix()} expected {expected}, got {reason}" + ) + continue + rejected += 1 + + extra = sorted( + path.name + for directory, expected in ( + (positive_root, set(POSITIVE_NAMES)), + (negative_root, set(NEGATIVE_REASONS)), + ) + for path in directory.iterdir() + if path.is_file() and path.name not in expected + ) + if extra: + failures.append("unexpected fixture files: " + ", ".join(extra)) + + if failures: + print("FAIL enterprise contract check") + for failure in failures: + print(f" {failure}") + return 1 + + print( + f"PASS enterprise document evidence envelope: positives={accepted} negatives={rejected}" + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/ci/google_attestation_contract_check.py b/tools/ci/google_attestation_contract_check.py new file mode 100644 index 0000000..2ce8dff --- /dev/null +++ b/tools/ci/google_attestation_contract_check.py @@ -0,0 +1,1427 @@ +#!/usr/bin/env python3 +"""Validate the V1 Google source-attestation contract without third-party packages.""" + +from __future__ import annotations + +import ast +import json +import re +import subprocess +import sys +import tempfile +from pathlib import Path +from typing import Any + +from enterprise_contract_check import ( + MAX_TEXT_BYTES, + canonical_bytes, + classify as classify_envelope, + envelope_digest, + is_id, + is_nonneg_int, + is_sha256, + keys_of, + load_json, + permute, + sha256_hex, + sorted_unique, +) + + +SCHEMA_VERSION = "1.0.0" +PROFILE_IDENTITY = "bran-google-enterprise-v1" +SCHEMA_ID = "https://schemas.alphazede.dev/bran/google-source-attestation/v1/schema.json" +SEMANTIC_ORACLE = "tools/ci/google_attestation_contract_check.py" +ADAPTER_RELATIVE = "tools/ci/document_ai_smoke_adapter.py" +RECORDED_DIR = "fixtures/google-attestation/recorded" +ADAPTER_ENVELOPE_NAME = "enterprise-document-evidence-envelope.json" +ADAPTER_ATTESTATION_NAME = "google-source-attestation.json" +RECORDED_PROCESSOR = "processor-create.json" +RECORDED_PROCESS = "process-layout.json" +RECORDED_PDF = "input.pdf" +RECORDED_MISSING_IDENTITY = "processor-missing-identity.json" +RECORDED_LEGACY_ONLY = "process-legacy-only.json" +RECORDED_EMPTY_LAYOUT = "process-empty-layout.json" +EXPECTED_PROCESSOR_LOCATOR = ( + "projects/synthetic-project/locations/us/processors/synthetic-processor/" + "processorVersions/pretrained-layout-parser-v1.0-2024-06-03" +) +ENVELOPE_KIND = "enterprise-document-evidence-envelope" +DOCUMENT_AI_ENVELOPE = "fixtures/enterprise-documents/positive/pdf-fixed-layout.json" +ACCOUNT_PREFIX = "acct:opaque:" +MAX_ATTESTATION_BYTES = 1_048_576 +MAX_FINDINGS = 64 +MAX_FINDING_BYTES = 128 +MAX_FILTERS = 16 +MAX_RESULT_IDS = 64 + +GOOGLE_PRODUCTS = frozenset( + { + "agent-search", + "document-ai", + "gemini-enterprise", + "knowledge-catalog", + } +) +PRODUCTS = GOOGLE_PRODUCTS | {"bran-git", "bran-okf"} +CAPABILITY_STATES = frozenset( + { + "federated", + "git-okf", + "imported", + "indexed", + "metadata-only", + "parsed", + "unavailable", + } +) +PRODUCT_STATES = { + "agent-search": frozenset({"federated", "imported", "indexed", "unavailable"}), + "bran-git": frozenset({"git-okf", "unavailable"}), + "bran-okf": frozenset({"git-okf", "unavailable"}), + "document-ai": frozenset({"parsed", "unavailable"}), + "gemini-enterprise": frozenset({"federated", "imported", "indexed", "unavailable"}), + "knowledge-catalog": frozenset({"metadata-only", "unavailable"}), +} +PRODUCT_OUTPUT = { + "agent-search": "search-result", + "bran-git": "git-okf", + "bran-okf": "git-okf", + "document-ai": ENVELOPE_KIND, + "gemini-enterprise": "search-result", + "knowledge-catalog": "catalog-entry", +} +OUTPUT_KINDS = frozenset( + {ENVELOPE_KIND, "catalog-entry", "git-okf", "search-result", "unavailable"} +) +NORMALIZED_OUTPUT_KINDS = frozenset({"catalog-entry", "git-okf", "search-result"}) +GOOGLE_LOCATOR_TEMPLATE = { + "agent-search": ( + "projects", + "project", + "locations", + "location", + "collections", + "collection", + "dataStores", + "data_store", + ), + "document-ai": ( + "projects", + "project", + "locations", + "location", + "processors", + "processor", + "processorVersions", + "processor_version", + ), + "gemini-enterprise": ( + "projects", + "project", + "locations", + "location", + "collections", + "collection", + "engines", + "engine", + ), + "knowledge-catalog": ( + "projects", + "project", + "locations", + "location", + "entryGroups", + "entry_group", + "entries", + "entry", + ), +} +BRAN_EVIDENCE_PRODUCTS = frozenset({"bran-git", "bran-okf"}) +LOCATOR_SEGMENT = re.compile(r"[A-Za-z0-9@][A-Za-z0-9@._-]{0,126}$") +SCOPE_SEGMENT = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,126}$") +SNAPSHOT_IDENTITY = re.compile(r"(?:[0-9a-f]{40}|[0-9a-f]{64})$") +OPAQUE_LOCATOR = "opaque:outside-owner-allowlist" +REVISION_KINDS = frozenset({"entry", "generation", "revision", "unavailable"}) +PRODUCT_REVISION_KIND = { + "agent-search": "revision", + "bran-git": "revision", + "bran-okf": "revision", + "document-ai": "generation", + "gemini-enterprise": "revision", + "knowledge-catalog": "entry", +} +HISTORY_STATES = frozenset({"imported", "indexed"}) +CHECKPOINT_STATES = frozenset({"conflict", "current", "stale", "unavailable"}) +PERMISSION_STATUSES = frozenset({"attested", "partial", "unavailable"}) +COST_STATUSES = frozenset({"recorded", "unavailable"}) +QUOTA_STATES = frozenset({"exhausted", "unavailable", "within-quota"}) +PERIMETER_STATES = frozenset({"allowed", "denied", "unavailable"}) +NETWORK_STATES = frozenset({"disabled", "not-invoked"}) +FAILURE_CODES = ( + "completeness-overclaim", + "conflict", + "dlp-findings", + "history-incomplete", + "location-mismatch", + "mixed-revision", + "network-disabled", + "perimeter-denied", + "permission-unavailable", + "quota-exhausted", + "secret-reflection", + "stale", + "tenant-escape", + "unauthorized-action", +) +FAILURE_SET = frozenset(FAILURE_CODES) +CLASSIFICATION_VALUES = frozenset( + {"internal", "private", "public", "public-compatible"} +) +REQUIRED_KEYS = ( + "admission", + "attestation_digest", + "attestation_id", + "capability", + "checkpoint", + "cost", + "failures", + "output", + "permission", + "policy", + "product", + "profile", + "runtime", + "schema_version", + "source", + "tenancy", + "truncation", +) +SECRET_MARKERS = ( + "-----BEGIN ", + "AIza", + "X-Goog-Credential=", + "X-Goog-Signature=", + "access_token=", + "private_key", + "refresh_token=", + "ya29.", +) +ACTION_MARKERS = ( + "drive.files.create", + "drive.files.update", + "generateAccessToken", + "iam.serviceAccounts", + "setIamPolicy", +) +OFFLINE_IMPORTS = frozenset( + { + "http", + "http.client", + "requests", + "socket", + "ssl", + "urllib", + "urllib.request", + } +) +POSITIVE_NAMES = ( + "agent-search.json", + "bran-git.json", + "bran-okf.json", + "document-ai.json", + "gemini-enterprise.json", + "knowledge-catalog.json", +) +NEGATIVE_FAILURES = { + "completeness-overclaim.json": frozenset({"completeness-overclaim"}), + "conflict-mixed-revision.json": frozenset({"conflict", "mixed-revision"}), + "dlp-rejected.json": frozenset({"dlp-findings"}), + "incomplete-permission-revision.json": frozenset( + {"history-incomplete", "permission-unavailable", "stale"} + ), + "network-disabled.json": frozenset({"network-disabled"}), + "opaque-locator-missing-scope.json": frozenset( + {"location-mismatch", "tenant-escape"} + ), + "quota-location-perimeter.json": frozenset( + {"location-mismatch", "perimeter-denied", "quota-exhausted"} + ), + "tenant-escape-secret-action.json": frozenset( + {"secret-reflection", "tenant-escape", "unauthorized-action"} + ), +} + + +def bran_root() -> Path: + return Path(__file__).resolve().parents[2] + + +def attestation_digest(record: dict[str, Any]) -> str: + body = {key: value for key, value in record.items() if key != "attestation_digest"} + return sha256_hex(canonical_bytes(body)) + + +def filter_digest(filters: object) -> str: + return sha256_hex(canonical_bytes(filters)) + + +def configured_digest(tenancy: dict[str, Any], locator: str, filters: object) -> str: + return sha256_hex( + canonical_bytes( + { + "account_ref": tenancy["account_ref"], + "filters": filters, + "locator": locator, + "project": tenancy["project"], + "tenant": tenancy["tenant"], + } + ) + ) + + +def output_digest(normalized: object) -> str: + return sha256_hex(canonical_bytes(normalized)) + + +def checkpoint_digest(locator: str, revision_value: str, digest: str) -> str: + return sha256_hex( + canonical_bytes( + { + "locator": locator, + "output_digest": digest, + "revision": revision_value, + } + ) + ) + + +def walk_strings(value: object) -> list[str]: + found: list[str] = [] + pending: list[object] = [value] + while pending: + current = pending.pop() + if isinstance(current, str): + found.append(current) + elif isinstance(current, dict): + pending.extend(current.values()) + elif isinstance(current, list): + pending.extend(current) + return found + + +def _safe_segment(value: str, pattern: re.Pattern[str]) -> bool: + return bool(value) and ".." not in value and pattern.fullmatch(value) is not None + + +def _parse_template_locator( + locator: str, template: tuple[str, ...] +) -> dict[str, str] | None: + if any(marker in locator for marker in ("\\", "?", "#", " ")): + return None + parts = locator.split("/") + if len(parts) != len(template): + return None + parsed: dict[str, str] = {} + for index, expected in enumerate(template): + part = parts[index] + if index % 2 == 0: + if part != expected: + return None + continue + if not _safe_segment(part, LOCATOR_SEGMENT): + return None + parsed[expected] = part + return parsed + + +def _parse_bran_git_locator(locator: str) -> dict[str, str] | None: + prefix = "git:repository/" + if not locator.startswith(prefix): + return None + parts = locator[len(prefix) :].split("/") + if len(parts) != 3: + return None + repository, kind, snapshot = parts + if kind not in {"commit", "tree"}: + return None + if not _safe_segment(repository, SCOPE_SEGMENT): + return None + if SNAPSHOT_IDENTITY.fullmatch(snapshot) is None: + return None + return {"location": kind, "project": repository, "snapshot": snapshot} + + +def _parse_bran_okf_locator(locator: str) -> dict[str, str] | None: + prefix = "okf:bundle/" + if not locator.startswith(prefix): + return None + parts = locator[len(prefix) :].split("/") + if len(parts) != 3: + return None + bundle, token, snapshot = parts + if token != "snapshot": + return None + if not _safe_segment(bundle, SCOPE_SEGMENT): + return None + if SNAPSHOT_IDENTITY.fullmatch(snapshot) is None: + return None + return {"location": "snapshot", "project": bundle, "snapshot": snapshot} + + +def parse_product_locator(product: str, locator: str) -> dict[str, str] | None: + if not isinstance(locator, str) or not locator or len(locator) > 1024: + return None + if locator.startswith("/") or "\\" in locator or ".." in locator: + return None + template = GOOGLE_LOCATOR_TEMPLATE.get(product) + if template is not None: + parsed = _parse_template_locator(locator, template) + if parsed is None: + return None + return {"location": parsed["location"], "project": parsed["project"]} + if product == "bran-git": + return _parse_bran_git_locator(locator) + if product == "bran-okf": + return _parse_bran_okf_locator(locator) + return None + + +def project_from_locator(locator: str) -> str | None: + parsed = parse_product_locator("gemini-enterprise", locator) + if parsed is None: + parsed = parse_product_locator("agent-search", locator) + if parsed is None: + parsed = parse_product_locator("document-ai", locator) + if parsed is None: + parsed = parse_product_locator("knowledge-catalog", locator) + return None if parsed is None else parsed["project"] + + +def location_from_locator(locator: str) -> str | None: + parsed = parse_product_locator("gemini-enterprise", locator) + if parsed is None: + parsed = parse_product_locator("agent-search", locator) + if parsed is None: + parsed = parse_product_locator("document-ai", locator) + if parsed is None: + parsed = parse_product_locator("knowledge-catalog", locator) + return None if parsed is None else parsed["location"] + + +def admission_eligibility(failures: list[str]) -> tuple[str, str, str]: + if failures: + return "rejected", "ineligible", "ineligible" + return "admitted", "eligible", "eligible" + + +def envelope_path_error(path: object, *, allow_leaf: bool = False) -> str | None: + if not isinstance(path, str) or not path or len(path) > 256: + return "malformed-structure" + relative = Path(path) + if relative.is_absolute() or ".." in relative.parts or "\\" in path: + return "unsafe-locator" + if not path.endswith(".json"): + return "malformed-structure" + if path.startswith("fixtures/enterprise-documents/positive/"): + return None + if allow_leaf and "/" not in path: + return None + return "unsupported-evidence" + + +def infer_failures(value: dict[str, Any]) -> list[str]: + found: list[str] = [] + strings = walk_strings(value) + if any(marker in text for text in strings for marker in SECRET_MARKERS): + found.append("secret-reflection") + if any(marker in text for text in strings for marker in ACTION_MARKERS): + found.append("unauthorized-action") + product = value["product"]["name"] + tenancy = value["tenancy"] + locator = value["source"]["locator"] + parsed_locator = parse_product_locator(product, locator) + if parsed_locator is None or parsed_locator["project"] != tenancy["project"]: + found.append("tenant-escape") + if parsed_locator is None or parsed_locator["location"] != tenancy["location"]: + found.append("location-mismatch") + if tenancy["perimeter"] == "denied": + found.append("perimeter-denied") + if product in GOOGLE_PRODUCTS and value["runtime"]["network"] == "disabled": + found.append("network-disabled") + else: + if value["permission"]["status"] != "attested": + found.append("permission-unavailable") + attested = value["capability"]["attested"] + revision_state = value["source"]["revision"]["state"] + if attested in HISTORY_STATES and revision_state == "unavailable": + found.append("history-incomplete") + if value["permission"]["authorization_proof"] is True: + found.append("completeness-overclaim") + checkpoint_state = value["checkpoint"]["state"] + if checkpoint_state == "stale": + found.append("stale") + if checkpoint_state == "conflict": + found.append("conflict") + source_revision = value["source"]["revision"]["value"] + checkpoint_revision = value["checkpoint"]["revision_value"] + if ( + isinstance(source_revision, str) + and isinstance(checkpoint_revision, str) + and source_revision != checkpoint_revision + ): + found.append("mixed-revision") + if value["cost"]["quota"] == "exhausted": + found.append("quota-exhausted") + if value["policy"]["dlp"]["status"] == "findings": + found.append("dlp-findings") + return sorted(set(found)) + + +def structural_reason( + value: object, + *, + output_root: Path | None = None, + envelope: dict[str, Any] | None = None, +) -> str | None: + if not isinstance(value, dict): + return "malformed-structure" + if set(value) != set(REQUIRED_KEYS): + return "malformed-structure" + if value.get("schema_version") != SCHEMA_VERSION: + return "malformed-structure" + if not is_id(value.get("attestation_id")) or not is_sha256( + value.get("attestation_digest") + ): + return "malformed-structure" + + profile = value.get("profile") + if not keys_of(profile, {"identity", "version"}): + return "malformed-structure" + if profile["identity"] != PROFILE_IDENTITY or profile["version"] != SCHEMA_VERSION: + return "malformed-structure" + + product = value.get("product") + if not keys_of(product, {"name", "identity", "component", "version"}): + return "malformed-structure" + if product["name"] not in PRODUCTS: + return "unsupported-evidence" + for field in ("identity", "component", "version"): + if not isinstance(product[field], str) or not product[field].strip(): + return "malformed-structure" + if len(product[field]) > 512: + return "malformed-structure" + + capability = value.get("capability") + if not keys_of(capability, {"requested", "effective", "attested"}): + return "malformed-structure" + for field in ("requested", "effective", "attested"): + if capability[field] not in CAPABILITY_STATES: + return "unsupported-evidence" + if capability["attested"] not in PRODUCT_STATES[product["name"]]: + return "unsupported-evidence" + if capability["attested"] == "unavailable" and capability["effective"] != "unavailable": + return "malformed-structure" + + tenancy = value.get("tenancy") + if not keys_of( + tenancy, {"tenant", "project", "location", "account_ref", "perimeter"} + ): + return "malformed-structure" + for field in ("tenant", "project", "location"): + if not isinstance(tenancy[field], str) or not tenancy[field].strip(): + return "malformed-structure" + if len(tenancy[field]) > 128: + return "malformed-structure" + account_ref = tenancy["account_ref"] + if ( + not isinstance(account_ref, str) + or not account_ref.startswith(ACCOUNT_PREFIX) + or not is_id(account_ref) + ): + return "malformed-structure" + if tenancy["perimeter"] not in PERIMETER_STATES: + return "malformed-structure" + + source = value.get("source") + if not keys_of( + source, + {"locator", "filters", "configured_digest", "filter_digest", "revision"}, + ): + return "malformed-structure" + locator = source["locator"] + if not isinstance(locator, str) or not locator.strip() or len(locator) > 1024: + return "malformed-structure" + filters = source["filters"] + if not isinstance(filters, dict) or not (1 <= len(filters) <= MAX_FILTERS): + return "malformed-structure" + for key, item in filters.items(): + if not isinstance(key, str) or not key or not isinstance(item, str) or not item: + return "malformed-structure" + if len(key) > 64 or len(item) > 256: + return "malformed-structure" + if not is_sha256(source["configured_digest"]) or not is_sha256(source["filter_digest"]): + return "malformed-structure" + if source["filter_digest"] != filter_digest(filters): + return "digest-mismatch" + if source["configured_digest"] != configured_digest(tenancy, locator, filters): + return "digest-mismatch" + revision = source["revision"] + if not keys_of(revision, {"state", "kind", "value"}): + return "malformed-structure" + if revision["state"] == "unavailable": + if revision["kind"] != "unavailable" or revision["value"] is not None: + return "malformed-structure" + elif revision["state"] == "attested": + if revision["kind"] != PRODUCT_REVISION_KIND[product["name"]]: + return "unsupported-evidence" + if not isinstance(revision["value"], str) or not revision["value"].strip(): + return "malformed-structure" + if len(revision["value"]) > 256: + return "malformed-structure" + else: + return "malformed-structure" + if revision["kind"] not in REVISION_KINDS: + return "malformed-structure" + parsed_locator = parse_product_locator(product["name"], locator) + if ( + product["name"] in BRAN_EVIDENCE_PRODUCTS + and revision["state"] == "attested" + and parsed_locator is not None + and parsed_locator.get("snapshot") != revision["value"] + ): + return "malformed-structure" + + permission = value.get("permission") + if not keys_of(permission, {"status", "authorization_proof"}): + return "malformed-structure" + if permission["status"] not in PERMISSION_STATUSES: + return "malformed-structure" + if type(permission["authorization_proof"]) is not bool: + return "malformed-structure" + + output = value.get("output") + if not keys_of(output, {"kind", "digest", "envelope_path", "normalized"}): + return "malformed-structure" + if output["kind"] not in OUTPUT_KINDS: + return "unsupported-evidence" + attested = capability["attested"] + if attested == "unavailable": + if ( + output["kind"] != "unavailable" + or output["digest"] is not None + or output["envelope_path"] is not None + or output["normalized"] is not None + ): + return "malformed-structure" + elif output["kind"] != PRODUCT_OUTPUT[product["name"]]: + return "unsupported-evidence" + + envelope_path = output["envelope_path"] + normalized = output["normalized"] + digest = output["digest"] + if output["kind"] == ENVELOPE_KIND: + path_error = envelope_path_error( + envelope_path, + allow_leaf=output_root is not None or envelope is not None, + ) + if path_error is not None: + return path_error + if normalized is not None or not is_sha256(digest): + return "malformed-structure" + loaded = envelope + if loaded is None: + envelope_file = (output_root if output_root is not None else bran_root()) / envelope_path + if not envelope_file.is_file(): + return "unsupported-evidence" + try: + loaded = load_json(envelope_file) + except ValueError: + return "malformed-structure" + if classify_envelope(loaded) is not None: + return "unsupported-evidence" + if not isinstance(loaded, dict): + return "malformed-structure" + parser = loaded.get("parser") + if not isinstance(parser, dict): + return "unsupported-evidence" + if parser.get("identity") != product["identity"]: + return "unsupported-evidence" + if parser.get("processor") != product["component"]: + return "unsupported-evidence" + if loaded.get("envelope_digest") != digest: + return "digest-mismatch" + if envelope_digest(loaded) != digest: + return "digest-mismatch" + elif output["kind"] in NORMALIZED_OUTPUT_KINDS: + if envelope_path is not None or not is_sha256(digest): + return "malformed-structure" + if not keys_of(normalized, {"item_count", "result_ids"}): + return "malformed-structure" + result_ids = normalized["result_ids"] + if not isinstance(result_ids, list) or not (1 <= len(result_ids) <= MAX_RESULT_IDS): + return "oversized" if isinstance(result_ids, list) and len(result_ids) > MAX_RESULT_IDS else "malformed-structure" + if not all(is_id(item) for item in result_ids) or not sorted_unique(result_ids): + return "malformed-structure" + if type(normalized["item_count"]) is not int or normalized["item_count"] != len( + result_ids + ): + return "malformed-structure" + if digest != output_digest(normalized): + return "digest-mismatch" + elif output["kind"] != "unavailable": + return "unsupported-evidence" + + checkpoint = value.get("checkpoint") + if not keys_of(checkpoint, {"id", "digest", "state", "revision_value"}): + return "malformed-structure" + if not is_id(checkpoint["id"]) or checkpoint["state"] not in CHECKPOINT_STATES: + return "malformed-structure" + if checkpoint["state"] == "unavailable": + if checkpoint["digest"] is not None or checkpoint["revision_value"] is not None: + return "malformed-structure" + else: + if not is_sha256(checkpoint["digest"]): + return "malformed-structure" + if checkpoint["state"] == "current": + if revision["state"] == "attested": + if checkpoint["revision_value"] != revision["value"]: + return "malformed-structure" + if not isinstance(digest, str) or checkpoint["digest"] != checkpoint_digest( + locator, revision["value"], digest + ): + return "digest-mismatch" + elif checkpoint["revision_value"] is not None: + return "malformed-structure" + elif checkpoint["revision_value"] is not None: + if not isinstance(checkpoint["revision_value"], str) or not checkpoint[ + "revision_value" + ].strip(): + return "malformed-structure" + if len(checkpoint["revision_value"]) > 256: + return "malformed-structure" + + truncation = value.get("truncation") + if not keys_of( + truncation, {"truncated", "omitted_bytes", "omitted_item_count", "reason"} + ): + return "malformed-structure" + if type(truncation["truncated"]) is not bool: + return "malformed-structure" + if not is_nonneg_int(truncation["omitted_bytes"], MAX_ATTESTATION_BYTES): + return "malformed-structure" + if not is_nonneg_int(truncation["omitted_item_count"], MAX_RESULT_IDS): + return "malformed-structure" + if truncation["truncated"]: + if not isinstance(truncation["reason"], str) or not truncation["reason"].strip(): + return "malformed-structure" + if len(truncation["reason"]) > 256: + return "malformed-structure" + elif ( + truncation["reason"] is not None + or truncation["omitted_bytes"] != 0 + or truncation["omitted_item_count"] != 0 + ): + return "malformed-structure" + + cost = value.get("cost") + if not keys_of(cost, {"status", "quota"}): + return "malformed-structure" + if cost["status"] not in COST_STATUSES or cost["quota"] not in QUOTA_STATES: + return "malformed-structure" + if cost["status"] == "unavailable" and cost["quota"] != "unavailable": + return "malformed-structure" + + runtime = value.get("runtime") + if not keys_of(runtime, {"network"}): + return "malformed-structure" + if runtime["network"] not in NETWORK_STATES: + return "malformed-structure" + if ( + runtime["network"] == "disabled" + and product["name"] in GOOGLE_PRODUCTS + and capability["attested"] != "unavailable" + ): + return "malformed-structure" + + policy = value.get("policy") + if not keys_of(policy, {"classification", "dlp", "public_boundary"}): + return "malformed-structure" + classification = policy["classification"] + if not keys_of(classification, {"status", "value"}): + return "malformed-structure" + if classification["status"] == "unavailable": + if classification["value"] is not None: + return "malformed-structure" + elif classification["status"] == "evaluated": + if classification["value"] not in CLASSIFICATION_VALUES: + return "malformed-structure" + else: + return "malformed-structure" + dlp = policy["dlp"] + if not keys_of(dlp, {"status", "findings"}): + return "malformed-structure" + if dlp["status"] not in {"findings", "not-evaluated", "passed"}: + return "malformed-structure" + findings = dlp["findings"] + if not isinstance(findings, list) or len(findings) > MAX_FINDINGS: + return "malformed-structure" if not isinstance(findings, list) else "oversized" + if not all( + isinstance(item, str) and item.strip() and len(item) <= MAX_FINDING_BYTES + for item in findings + ): + return "malformed-structure" + if not sorted_unique(findings): + return "malformed-structure" + if dlp["status"] == "findings": + if not findings: + return "malformed-structure" + elif findings: + return "malformed-structure" + boundary = policy["public_boundary"] + if not keys_of(boundary, {"outcome", "value"}): + return "malformed-structure" + if boundary["outcome"] not in {"admit-export", "reject-export", "unavailable"}: + return "malformed-structure" + if boundary["outcome"] == "unavailable": + if boundary["value"] is not None: + return "malformed-structure" + elif boundary["value"] not in CLASSIFICATION_VALUES: + return "malformed-structure" + if boundary["outcome"] == "admit-export" and boundary["value"] != "public": + return "malformed-structure" + + failures = value.get("failures") + if not isinstance(failures, list) or len(failures) > len(FAILURE_CODES): + return "malformed-structure" + if not all(isinstance(item, str) and item in FAILURE_SET for item in failures): + return "unsupported-evidence" + if not sorted_unique(failures): + return "malformed-structure" + + admission = value.get("admission") + if not keys_of(admission, {"status", "packet", "query", "reasons"}): + return "malformed-structure" + if admission["status"] not in {"admitted", "rejected"}: + return "malformed-structure" + if admission["packet"] not in {"eligible", "ineligible"}: + return "malformed-structure" + if admission["query"] not in {"eligible", "ineligible"}: + return "malformed-structure" + reasons = admission["reasons"] + if not isinstance(reasons, list) or not all( + isinstance(item, str) and item.strip() and len(item) <= 128 for item in reasons + ): + return "malformed-structure" + if not sorted_unique(reasons): + return "malformed-structure" + + computed = infer_failures(value) + if failures != computed: + return "completeness-overclaim" + if computed: + if ( + admission["status"] != "rejected" + or admission["packet"] != "ineligible" + or admission["query"] != "ineligible" + or reasons != computed + ): + return "malformed-structure" + elif ( + admission["status"] != "admitted" + or admission["packet"] != "eligible" + or admission["query"] != "eligible" + or reasons + ): + return "malformed-structure" + + if len(canonical_bytes(value)) > MAX_ATTESTATION_BYTES: + return "oversized" + if value["attestation_digest"] != attestation_digest(value): + return "digest-mismatch" + return None + + +def evaluate( + value: object, + *, + output_root: Path | None = None, + envelope: dict[str, Any] | None = None, +) -> tuple[str | None, list[str]]: + reason = structural_reason(value, output_root=output_root, envelope=envelope) + if reason is not None: + return reason, [] + assert isinstance(value, dict) + return None, list(value["failures"]) + + +def with_digests(record: dict[str, Any]) -> dict[str, Any]: + filled = json_clone(record) + source = filled["source"] + tenancy = filled["tenancy"] + source["filter_digest"] = filter_digest(source["filters"]) + source["configured_digest"] = configured_digest( + tenancy, source["locator"], source["filters"] + ) + output = filled["output"] + if output["kind"] == ENVELOPE_KIND: + envelope = load_json(bran_root() / output["envelope_path"]) + output["digest"] = envelope["envelope_digest"] + output["normalized"] = None + elif output["kind"] in NORMALIZED_OUTPUT_KINDS: + output["digest"] = output_digest(output["normalized"]) + output["envelope_path"] = None + else: + output["digest"] = None + output["envelope_path"] = None + output["normalized"] = None + checkpoint = filled["checkpoint"] + revision = source["revision"] + if ( + checkpoint["state"] == "current" + and revision["state"] == "attested" + and isinstance(output["digest"], str) + ): + checkpoint["revision_value"] = revision["value"] + checkpoint["digest"] = checkpoint_digest( + source["locator"], revision["value"], output["digest"] + ) + filled["attestation_digest"] = attestation_digest(filled) + return filled + + +def json_clone(value: dict[str, Any]) -> dict[str, Any]: + return json.loads(json.dumps(value)) + + +def validate_schema(schema: object) -> list[str]: + errors: list[str] = [] + if not isinstance(schema, dict): + return ["schema root must be an object"] + if schema.get("$id") != SCHEMA_ID: + errors.append("schema $id is not the V1 Google source-attestation id") + if schema.get("x-semantic-oracle") != SEMANTIC_ORACLE: + errors.append("schema missing or incorrect x-semantic-oracle annotation") + required = schema.get("required") + if required != list(REQUIRED_KEYS): + errors.append("schema required keys drifted from the semantic oracle") + capabilities = schema.get("x-product-capabilities") + if not isinstance(capabilities, dict): + errors.append("schema missing x-product-capabilities") + else: + expected = { + name: sorted(states) for name, states in sorted(PRODUCT_STATES.items()) + } + actual = { + name: list(states) if isinstance(states, list) else states + for name, states in capabilities.items() + } + if actual != expected: + errors.append("schema x-product-capabilities drifted from the oracle") + properties = schema.get("properties") + if not isinstance(properties, dict): + errors.append("schema properties must be an object") + return errors + version = properties.get("schema_version", {}) + if not isinstance(version, dict) or version.get("const") != SCHEMA_VERSION: + errors.append("schema_version const drifted from 1.0.0") + product = properties.get("product", {}) + name = ( + product.get("properties", {}).get("name", {}) + if isinstance(product, dict) + else {} + ) + if not isinstance(name, dict) or set(name.get("enum", [])) != set(PRODUCTS): + errors.append("product.name enum drifted from the V1 managed-product set") + if set(PRODUCT_OUTPUT) != set(PRODUCTS): + errors.append("PRODUCT_OUTPUT drifted from the schema-declared product set") + locator_products = set(GOOGLE_LOCATOR_TEMPLATE) | BRAN_EVIDENCE_PRODUCTS + if locator_products != set(PRODUCTS): + errors.append("locator grammars drifted from the schema-declared product set") + if set(PRODUCT_OUTPUT.values()) - (OUTPUT_KINDS - {"unavailable"}): + errors.append("PRODUCT_OUTPUT contains an undeclared output kind") + output = properties.get("output", {}) + kind = ( + output.get("properties", {}).get("kind", {}) if isinstance(output, dict) else {} + ) + if not isinstance(kind, dict) or set(kind.get("enum", [])) != set(OUTPUT_KINDS): + errors.append("output.kind enum drifted from the V1 output set") + failures = properties.get("failures", {}) + items = failures.get("items", {}) if isinstance(failures, dict) else {} + if not isinstance(items, dict) or items.get("enum") != list(FAILURE_CODES): + errors.append("failures enum drifted from the typed failure vocabulary") + return errors + + +def assert_offline_imports(root: Path) -> str | None: + for relative in (SEMANTIC_ORACLE, ADAPTER_RELATIVE): + source = (root / relative).read_text(encoding="utf-8") + tree = ast.parse(source) + imported: set[str] = set() + for node in ast.walk(tree): + if isinstance(node, ast.Import): + imported.update(alias.name.split(".", 1)[0] for alias in node.names) + imported.update(alias.name for alias in node.names) + elif isinstance(node, ast.ImportFrom) and node.module: + imported.add(node.module.split(".", 1)[0]) + imported.add(node.module) + blocked = sorted(imported & OFFLINE_IMPORTS) + if blocked: + return f"{relative} imports provider or network modules: " + ", ".join(blocked) + return None + + +def load_named_json(path: Path) -> tuple[object | None, str | None]: + try: + return load_json(path), None + except ValueError: + return None, "malformed-structure" + + +def invoke_recorded_adapter( + root: Path, + output_dir: Path, + processor: Path, + process: Path, + pdf: Path, +) -> subprocess.CompletedProcess[str]: + return subprocess.run( + [ + sys.executable, + str(root / ADAPTER_RELATIVE), + "--processor-json", + str(processor), + "--process-json", + str(process), + "--input-pdf", + str(pdf), + "--output-dir", + str(output_dir), + "--account-ref", + "acct:opaque:synthetic-001", + "--tenant", + "synthetic-tenant", + ], + cwd=str(root), + capture_output=True, + text=True, + ) + + +def recorded_adapter_errors(root: Path) -> list[str]: + recorded = root / RECORDED_DIR + required = { + RECORDED_PROCESSOR: recorded / RECORDED_PROCESSOR, + RECORDED_PROCESS: recorded / RECORDED_PROCESS, + RECORDED_PDF: recorded / RECORDED_PDF, + RECORDED_MISSING_IDENTITY: recorded / RECORDED_MISSING_IDENTITY, + RECORDED_LEGACY_ONLY: recorded / RECORDED_LEGACY_ONLY, + RECORDED_EMPTY_LAYOUT: recorded / RECORDED_EMPTY_LAYOUT, + ADAPTER_RELATIVE: root / ADAPTER_RELATIVE, + } + missing = [ + path.as_posix() + for path in required.values() + if not path.is_file() + ] + if missing: + return [f"missing recorded adapter input: {path}" for path in missing] + + errors: list[str] = [] + with tempfile.TemporaryDirectory() as first, tempfile.TemporaryDirectory() as second: + first_dir = Path(first) + second_dir = Path(second) + first_run = invoke_recorded_adapter( + root, + first_dir, + required[RECORDED_PROCESSOR], + required[RECORDED_PROCESS], + required[RECORDED_PDF], + ) + if first_run.returncode != 0: + detail = first_run.stderr.strip() or first_run.stdout.strip() or "no adapter output" + return [f"recorded adapter failed: {detail}"] + second_run = invoke_recorded_adapter( + root, + second_dir, + required[RECORDED_PROCESSOR], + required[RECORDED_PROCESS], + required[RECORDED_PDF], + ) + if second_run.returncode != 0: + detail = second_run.stderr.strip() or second_run.stdout.strip() or "no adapter output" + return [f"recorded adapter replay failed: {detail}"] + for name in (ADAPTER_ENVELOPE_NAME, ADAPTER_ATTESTATION_NAME): + left = first_dir / name + right = second_dir / name + if not left.is_file() or not right.is_file(): + errors.append(f"recorded adapter did not write {name}") + continue + if left.read_bytes() != right.read_bytes(): + errors.append(f"recorded adapter replay bytes differ for {name}") + if errors: + return errors + envelope = load_json(first_dir / ADAPTER_ENVELOPE_NAME) + envelope_reason = classify_envelope(envelope) + if envelope_reason is not None: + errors.append( + f"recorded adapter envelope is structurally invalid: {envelope_reason}" + ) + elif not isinstance(envelope, dict): + errors.append("recorded adapter envelope is not an object") + else: + admission = envelope.get("admission") + required_reasons = { + "dlp-not-evaluated", + "hazard-evidence-unavailable", + "response-input-binding-unavailable", + } + if not isinstance(admission, dict): + errors.append("recorded adapter envelope admission is missing") + else: + reasons = admission.get("reasons") + if admission.get("status") != "rejected": + errors.append("recorded adapter envelope must be rejected") + if admission.get("packet") != "ineligible": + errors.append("recorded adapter envelope packet must be ineligible") + if admission.get("query") != "ineligible": + errors.append("recorded adapter envelope query must be ineligible") + if not isinstance(reasons, list) or not required_reasons <= set(reasons): + errors.append( + "recorded adapter envelope missing unavailable-check reasons" + ) + pdf_digest = sha256_hex(required[RECORDED_PDF].read_bytes()) + original = envelope.get("original") + source = envelope.get("source") + if not isinstance(original, dict) or original.get("sha256") != pdf_digest: + errors.append("recorded adapter envelope original digest drifted") + if not isinstance(source, dict) or source.get("locator") != f"sha256:{pdf_digest}": + errors.append("recorded adapter envelope source is not the input digest") + attestation, parse_reason = load_named_json(first_dir / ADAPTER_ATTESTATION_NAME) + reason, typed = ( + (parse_reason, []) + if parse_reason is not None + else evaluate(attestation, output_root=first_dir) + ) + if reason is not None: + errors.append(f"recorded adapter attestation is structurally invalid: {reason}") + elif not isinstance(attestation, dict): + errors.append("recorded adapter attestation is not an object") + else: + if attestation["source"]["locator"] != EXPECTED_PROCESSOR_LOCATOR: + errors.append("recorded adapter locator is not the saved processor version") + if attestation["runtime"]["network"] != "not-invoked": + errors.append("recorded adapter must record runtime.network=not-invoked") + if attestation["permission"]["status"] == "attested": + errors.append("recorded adapter invented permission evidence") + if attestation["source"]["revision"]["state"] == "attested": + errors.append("recorded adapter invented revision evidence") + if attestation["cost"]["status"] != "unavailable": + errors.append("recorded adapter invented cost evidence") + if attestation["tenancy"]["perimeter"] != "unavailable": + errors.append("recorded adapter invented perimeter evidence") + if attestation["capability"]["requested"] != "parsed": + errors.append("recorded adapter requested capability drifted") + if attestation["capability"]["effective"] != "parsed": + errors.append("recorded adapter effective capability drifted") + if attestation["capability"]["attested"] != "parsed": + errors.append("recorded adapter attested capability drifted") + if attestation["admission"]["status"] != "rejected": + errors.append("recorded adapter attestation must remain rejected") + if attestation["admission"]["packet"] != "ineligible": + errors.append("recorded adapter attestation packet must be ineligible") + if "permission-unavailable" not in typed: + errors.append("recorded adapter must leave permission unavailable") + closed = ( + ("missing-identity", required[RECORDED_MISSING_IDENTITY], required[RECORDED_PROCESS]), + ("legacy-only", required[RECORDED_PROCESSOR], required[RECORDED_LEGACY_ONLY]), + ("empty-layout", required[RECORDED_PROCESSOR], required[RECORDED_EMPTY_LAYOUT]), + ) + for label, processor, process in closed: + dest = first_dir / label + dest.mkdir() + result = invoke_recorded_adapter( + root, + dest, + processor, + process, + required[RECORDED_PDF], + ) + if result.returncode == 0: + errors.append(f"recorded adapter accepted {label}") + mismatch = load_json(required[RECORDED_PROCESS]) + if isinstance(mismatch, dict) and isinstance(mismatch.get("document"), dict): + mismatch["document"]["mimeType"] = "text/plain" + mismatch_path = first_dir / "process-mime-mismatch.json" + mismatch_path.write_bytes(canonical_bytes(mismatch) + b"\n") + mime_run = invoke_recorded_adapter( + root, + first_dir / "mime-mismatch", + required[RECORDED_PROCESSOR], + mismatch_path, + required[RECORDED_PDF], + ) + if mime_run.returncode == 0: + errors.append("recorded adapter accepted mime-mismatch") + else: + errors.append("recorded process fixture missing document") + errors.extend(recorded_oversized_text_errors(root, first_dir, required)) + errors.extend(recorded_overwrite_errors(root, first_dir, required)) + return errors + + +def recorded_oversized_text_errors( + root: Path, + work_dir: Path, + required: dict[str, Path], +) -> list[str]: + extra = 64 + oversized = { + "document": { + "documentLayout": { + "blocks": [ + { + "blockId": "1", + "pageSpan": {"pageEnd": 1, "pageStart": 1}, + "textBlock": { + "text": "A" * (MAX_TEXT_BYTES + extra), + "type": "paragraph", + }, + } + ] + } + } + } + process_path = work_dir / "process-oversized-text.json" + process_path.write_bytes(canonical_bytes(oversized) + b"\n") + dest = work_dir / "oversized-text" + result = invoke_recorded_adapter( + root, + dest, + required[RECORDED_PROCESSOR], + process_path, + required[RECORDED_PDF], + ) + if result.returncode != 0: + detail = result.stderr.strip() or result.stdout.strip() or "no adapter output" + return [f"recorded adapter rejected oversized-text probe: {detail}"] + errors: list[str] = [] + envelope, envelope_parse = load_named_json(dest / ADAPTER_ENVELOPE_NAME) + if envelope_parse is not None: + return [f"oversized-text envelope is malformed: {envelope_parse}"] + envelope_reason = classify_envelope(envelope) + if envelope_reason is not None: + return [f"oversized-text envelope is structurally invalid: {envelope_reason}"] + attestation, attestation_parse = load_named_json(dest / ADAPTER_ATTESTATION_NAME) + reason, _typed = ( + (attestation_parse, []) + if attestation_parse is not None + else evaluate(attestation, output_root=dest) + ) + if reason is not None: + return [f"oversized-text attestation is structurally invalid: {reason}"] + if not isinstance(envelope, dict) or not isinstance(attestation, dict): + return ["oversized-text outputs are not objects"] + envelope_truncation = envelope.get("receipts", {}) + if isinstance(envelope_truncation, dict): + envelope_truncation = envelope_truncation.get("truncation") + attestation_truncation = attestation.get("truncation") + if not isinstance(envelope_truncation, dict) or not isinstance( + attestation_truncation, dict + ): + return ["oversized-text truncation receipts are missing"] + omitted_bytes = envelope_truncation.get("omitted_bytes") + omitted_items = envelope_truncation.get("omitted_anchor_count") + if envelope_truncation.get("truncated") is not True or omitted_bytes in {None, 0}: + errors.append("oversized-text envelope did not report nonzero omission") + if ( + attestation_truncation.get("truncated") != envelope_truncation.get("truncated") + or attestation_truncation.get("omitted_bytes") != omitted_bytes + or attestation_truncation.get("omitted_item_count") != omitted_items + or attestation_truncation.get("reason") != envelope_truncation.get("reason") + ): + errors.append("oversized-text #13 truncation disagrees with #5") + return errors + + +def recorded_overwrite_errors( + root: Path, + work_dir: Path, + required: dict[str, Path], +) -> list[str]: + dest = work_dir / "sentinel-overwrite" + dest.mkdir() + envelope_path = dest / ADAPTER_ENVELOPE_NAME + attestation_path = dest / ADAPTER_ATTESTATION_NAME + envelope_bytes = b"sentinel-envelope\n" + attestation_bytes = b"sentinel-attestation\n" + envelope_path.write_bytes(envelope_bytes) + attestation_path.write_bytes(attestation_bytes) + result = invoke_recorded_adapter( + root, + dest, + required[RECORDED_PROCESSOR], + required[RECORDED_PROCESS], + required[RECORDED_PDF], + ) + errors: list[str] = [] + if result.returncode == 0: + errors.append("recorded adapter overwrote existing outputs") + if envelope_path.read_bytes() != envelope_bytes: + errors.append("recorded adapter mutated existing envelope bytes") + if attestation_path.read_bytes() != attestation_bytes: + errors.append("recorded adapter mutated existing attestation bytes") + return errors + + +def main() -> int: + root = bran_root() + offline_error = assert_offline_imports(root) + if offline_error is not None: + print(f"FAIL google attestation contract check: {offline_error}") + return 1 + + schema_path = root / "schemas/google-source-attestation.schema.json" + fixture_root = root / "fixtures/google-attestation" + positive_root = fixture_root / "positive" + negative_root = fixture_root / "negative" + try: + schema = load_json(schema_path) + except ValueError as error: + print(f"FAIL google attestation contract check: {error}") + return 1 + + schema_errors = validate_schema(schema) + if schema_errors: + print("FAIL google attestation contract check: schema drifted") + for error in schema_errors: + print(f" {error}") + return 1 + + contract_errors: list[str] = [] + for name in sorted(PRODUCTS): + if name not in PRODUCT_OUTPUT: + contract_errors.append(f"missing output contract for {name}") + if parse_product_locator(name, OPAQUE_LOCATOR) is not None: + contract_errors.append(f"{name} accepted an unrecognized locator") + if contract_errors: + print("FAIL google attestation contract check: product contract incomplete") + for error in contract_errors: + print(f" {error}") + return 1 + + missing = [ + path.as_posix() + for path in ( + [positive_root / name for name in POSITIVE_NAMES] + + [negative_root / name for name in NEGATIVE_FAILURES] + ) + if not path.is_file() + ] + if missing: + print("FAIL google attestation contract check: missing fixtures") + for path in missing: + print(f" {path}") + return 1 + + failures: list[str] = [] + accepted = 0 + golden_bytes: bytes | None = None + golden_digest: str | None = None + for name in POSITIVE_NAMES: + path = positive_root / name + record, parse_reason = load_named_json(path) + reason, typed = ( + (parse_reason, []) if parse_reason is not None else evaluate(record) + ) + if reason is not None or typed: + shown = reason if reason is not None else ",".join(typed) + failures.append(f"{path.relative_to(root).as_posix()} rejected as {shown}") + continue + assert isinstance(record, dict) + if record["admission"]["status"] != "admitted": + failures.append( + f"{path.relative_to(root).as_posix()} is structurally valid but not admitted" + ) + continue + if name == "document-ai.json" and record["output"]["envelope_path"] != DOCUMENT_AI_ENVELOPE: + failures.append("document-ai positive must reference the integrated #5 PDF envelope") + continue + accepted += 1 + if golden_bytes is None: + golden_bytes = canonical_bytes(record) + golden_digest = record["attestation_digest"] + permuted = permute(record) + if canonical_bytes(permuted) != golden_bytes: + failures.append( + "permuted in-memory JSON did not serialize to the golden canonical bytes" + ) + if not isinstance(permuted, dict) or attestation_digest(permuted) != golden_digest: + failures.append( + "permuted in-memory JSON did not reproduce the golden attestation digest" + ) + + gemini_path = positive_root / "gemini-enterprise.json" + gemini_record, gemini_reason = load_named_json(gemini_path) + if gemini_reason is not None or not isinstance(gemini_record, dict): + failures.append("gemini-enterprise positive is required for locator closure") + else: + opaque_record = with_digests(gemini_record) + opaque_record["source"]["locator"] = OPAQUE_LOCATOR + opaque_record = with_digests(opaque_record) + opaque_failures = infer_failures(opaque_record) + if not {"location-mismatch", "tenant-escape"} <= set(opaque_failures): + failures.append( + "opaque locator did not emit location-mismatch and tenant-escape" + ) + _status, packet_state, query_state = admission_eligibility(opaque_failures) + if packet_state != "ineligible" or query_state != "ineligible": + failures.append("opaque locator remained packet or query eligible") + + rejected = 0 + for name, expected in NEGATIVE_FAILURES.items(): + path = negative_root / name + record, parse_reason = load_named_json(path) + reason, typed = ( + (parse_reason, []) if parse_reason is not None else evaluate(record) + ) + if reason is not None: + failures.append( + f"{path.relative_to(root).as_posix()} expected {','.join(sorted(expected))}, got {reason}" + ) + continue + if frozenset(typed) != expected: + failures.append( + f"{path.relative_to(root).as_posix()} expected {','.join(sorted(expected))}, got {','.join(typed)}" + ) + continue + rejected += 1 + + extra = sorted( + path.name + for directory, expected_names in ( + (positive_root, set(POSITIVE_NAMES)), + (negative_root, set(NEGATIVE_FAILURES)), + ) + for path in directory.iterdir() + if path.is_file() and path.name not in expected_names + ) + if extra: + failures.append("unexpected fixture files: " + ", ".join(extra)) + + failures.extend(recorded_adapter_errors(root)) + + if failures: + print("FAIL google attestation contract check") + for failure in failures: + print(f" {failure}") + return 1 + + print( + f"PASS google source attestation: positives={accepted} negatives={rejected}" + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/ci/test-budget.json b/tools/ci/test-budget.json index 35f2006..da92370 100644 --- a/tools/ci/test-budget.json +++ b/tools/ci/test-budget.json @@ -263,6 +263,61 @@ "source": "crates/bran-core/src/agent/coordinator.rs", "support_fixtures": [] }, + { + "stable_id": "P3-ENTERPRISE-DOCUMENT", + "phase": 3, + "slice": "3.4", + "runner": "python", + "name": "tools/ci/enterprise_contract_check.py", + "category": "contract", + "classification": "new", + "source": "tools/ci/enterprise_contract_check.py", + "support_fixtures": [ + "fixtures/enterprise-documents/negative/active-content.json", + "fixtures/enterprise-documents/negative/digest-mismatch.json", + "fixtures/enterprise-documents/negative/external-reference.json", + "fixtures/enterprise-documents/negative/malformed-structure.json", + "fixtures/enterprise-documents/negative/oversized.json", + "fixtures/enterprise-documents/negative/unsafe-asset-path.json", + "fixtures/enterprise-documents/negative/unsupported-evidence.json", + "fixtures/enterprise-documents/positive/docx-flow.json", + "fixtures/enterprise-documents/positive/pdf-fixed-layout.json", + "fixtures/enterprise-documents/positive/pptx-presentation.json", + "fixtures/enterprise-documents/positive/xlsx-grid.json" + ] + }, + { + "stable_id": "P3-GOOGLE-ATTESTATION", + "phase": 3, + "slice": "3.4", + "runner": "python", + "name": "tools/ci/google_attestation_contract_check.py", + "category": "contract", + "classification": "new", + "source": "tools/ci/google_attestation_contract_check.py", + "support_fixtures": [ + "fixtures/google-attestation/negative/completeness-overclaim.json", + "fixtures/google-attestation/negative/conflict-mixed-revision.json", + "fixtures/google-attestation/negative/dlp-rejected.json", + "fixtures/google-attestation/negative/incomplete-permission-revision.json", + "fixtures/google-attestation/negative/network-disabled.json", + "fixtures/google-attestation/negative/opaque-locator-missing-scope.json", + "fixtures/google-attestation/negative/quota-location-perimeter.json", + "fixtures/google-attestation/negative/tenant-escape-secret-action.json", + "fixtures/google-attestation/positive/agent-search.json", + "fixtures/google-attestation/positive/bran-git.json", + "fixtures/google-attestation/positive/bran-okf.json", + "fixtures/google-attestation/positive/document-ai.json", + "fixtures/google-attestation/positive/gemini-enterprise.json", + "fixtures/google-attestation/positive/knowledge-catalog.json", + "fixtures/google-attestation/recorded/input.pdf", + "fixtures/google-attestation/recorded/process-empty-layout.json", + "fixtures/google-attestation/recorded/process-layout.json", + "fixtures/google-attestation/recorded/process-legacy-only.json", + "fixtures/google-attestation/recorded/processor-create.json", + "fixtures/google-attestation/recorded/processor-missing-identity.json" + ] + }, { "stable_id": "P4-SEALED-RELEASE", "phase": 4,