From fc4dd77991378d36e51a3e7dc463be2841694660 Mon Sep 17 00:00:00 2001 From: vivekyarra Date: Tue, 27 Oct 2026 11:39:33 +0530 Subject: [PATCH 1/3] docs: lead with live AWS runs, real recordings and the Graviton benchmark --- README.md | 39 +++++++++++++++++++++++++++++++--- docs/JUDGE_GUIDE.md | 46 +++++++++++++++++----------------------- docs/SUBMISSION.md | 32 +++++++++++++++++----------- docs/TECHNICAL_REPORT.md | 4 ++-- 4 files changed, 77 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 60be666..f78a43a 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,44 @@ is not a deepfake classifier and not cryptographic proof. Entry for the OpenCV AI Competition 2026 (OpenCV 5 + AWS). The specification is in [SPEC.md](SPEC.md); current status is in [docs/PROGRESS.md](docs/PROGRESS.md). -- Live app (AWS, HTTPS): https://5u7suz00bi.execute-api.us-east-1.amazonaws.com — guide in [docs/JUDGE_GUIDE.md](docs/JUDGE_GUIDE.md) +## Live on AWS + +- **Live app (HTTPS):** https://5u7suz00bi.execute-api.us-east-1.amazonaws.com — walkthrough in [docs/JUDGE_GUIDE.md](docs/JUDGE_GUIDE.md) +- **9 AWS CDK stacks deployed** in `us-east-1`: API Gateway → VPC link → internal ALB → Fargate `linux/arm64` API, + Graviton4 `c8g` ECS workers fed by SQS, DynamoDB, S3 with KMS, Cognito reviewer group, IoT Core ingest, + CloudWatch dashboards and alarms (technical report §6). +- **Agent planner on Amazon Bedrock:** Amazon Nova Lite chooses the next OpenCV tool; code-enforced policies keep + every verdict deterministic and every step is traced (`results/aws//trace.json`). +- **Real iPhone 13 recording on the live deployment:** job `cd65b6ea0c20` ran end to end on a Graviton4 worker. The + stock camera app's anti-banding had removed the flicker bands, so the engine reported `INCONCLUSIVE` + (`SIGNAL_ABSENT`) instead of guessing, and the agent adapted by asking for the original file and the camera model. +- **End-to-end AWS runs** with full evidence (result, trace, report PDF, plots) under `results/aws/`. + +## Graviton4 vs x86 (stock OpenCV 5.0.0, `results/bench/2026-10-27/`) + +| Metric | x86 `c7i.2xlarge` | Graviton4 `c8g.2xlarge` | +|---|---|---| +| Throughput, video-s per wall-s | 0.92 | 1.20 | +| Cost per video-hour | $0.390 | $0.266 | + +Same engine, same input, five runs per configuration; Graviton4 processes more video per second at a lower cost per +hour of video (technical report §7). + +## Evaluation + +- **0 false `CONTRADICTED`** on authentic clips of the frozen-threshold test split (SYNTHETIC simulator clips). +- Agent traces for each behaviour: `results/agent_traces/`. +- Full SYNTHETIC tables and the real-recording runs: technical report §9. + +## Documents + - Technical report: [docs/TECHNICAL_REPORT.md](docs/TECHNICAL_REPORT.md) - Architecture: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) · diagrams in [docs/diagrams/](docs/diagrams/) -- Responsible use: [docs/RESPONSIBLE_USE.md](docs/RESPONSIBLE_USE.md) -- Video: the link is added at submission (at most 5 minutes) +- Video: the link is on the Devpost page. + +## Responsible use + +See [docs/RESPONSIBLE_USE.md](docs/RESPONSIBLE_USE.md). ## What is in the repository diff --git a/docs/JUDGE_GUIDE.md b/docs/JUDGE_GUIDE.md index 9aa732f..4902066 100644 --- a/docs/JUDGE_GUIDE.md +++ b/docs/JUDGE_GUIDE.md @@ -4,14 +4,20 @@ GRIDPRINT checks if a video is real by reading the power-grid frequency from the Endpoint: https://5u7suz00bi.execute-api.us-east-1.amazonaws.com -The endpoint is the live AWS deployment (`us-east-1`): Amazon API Gateway → internal Application Load Balancer → Fargate API, with jobs on Graviton4 ECS workers. The web app is served from the same address. +## What is running -**Live demonstration.** The team runs the full system with `make local` in a live screen-share on request. This runs the same engine, agent and web app as the AWS deployment, with local storage and the deterministic planner. +- **Live AWS deployment (`us-east-1`), 9 CDK stacks:** Amazon API Gateway → VPC link → internal Application Load Balancer → Fargate `linux/arm64` API; jobs run on Graviton4 `c8g` ECS workers fed by SQS; DynamoDB, S3 with KMS, Cognito reviewer group, IoT Core ingest, CloudWatch dashboards and alarms. The web app is served from the same address (technical report §6). +- **Agent planner on Amazon Bedrock (Amazon Nova Lite).** It picks the next OpenCV tool from the measurements; code-enforced policies keep the verdict deterministic and every step is traced. +- **Real iPhone 13 recording on the live deployment** (job `cd65b6ea0c20`). The Graviton4 worker ran the full pipeline. The stock camera app's anti-banding had removed the flicker bands, so the result is `INCONCLUSIVE` (`SIGNAL_ABSENT`) rather than a guess, and the agent adapted by asking for the original file and the camera model. +- **Graviton4 vs x86 benchmark** (stock OpenCV 5.0.0): 1.20 vs 0.92 video-seconds per wall-second , $0.266 vs $0.390 per hour of video (technical report §7). +- **0 false `CONTRADICTED`** on authentic clips of the frozen-threshold evaluation test split (SYNTHETIC simulator clips). Agent traces: `results/agent_traces/`. + +**Live walkthrough.** On request, the team also runs the full system with `make local` in a screen-share: the same engine, agent and web app as the AWS deployment, with local storage and the deterministic planner. ## 1. What you can do on the endpoint -1. Open the endpoint. The home page has three parts: upload, demo cases, and the live grid tile. -2. Run a demo case, or upload your own MP4 or MOV file. +1. Open the endpoint. The home page has three parts: upload, ready-made test cases, and the live grid tile. +2. Run a test case, or upload your own MP4 or MOV file. 3. Watch the job page. It shows progress, the live agent trace, the verdict card, the band overlay player with timeline markers, plots, and downloads (report PDF, report JSON, overlay MP4). 4. Log in as a reviewer to see the approval screen for `CONTRADICTED` results. @@ -19,14 +25,12 @@ The endpoint is the live AWS deployment (`us-east-1`): Amazon API Gateway → in 1. Open a finished job page directly, for example https://5u7suz00bi.execute-api.us-east-1.amazonaws.com/#/jobs/aws3-capture-verified-040158 - Other finished jobs: `#/jobs/aws3-dropped-frames-040218`, `#/jobs/aws3-cut-1s-040211`. + Other finished jobs: `#/jobs/aws3-dropped-frames-040218`, `#/jobs/cd65b6ea0c20` (real iPhone 13 recording). 2. Check the verdict card (verdict, reasons, recording-time estimate), the plots, and the agent trace. Each trace step shows the planner, the tool, and the decision reason; steps where the code guard replaced the model's proposal start with "guard:". 3. Download the report PDF or JSON from the job page and compare it with the copy in the repository under `results/aws//` (`result.json`, `trace.json`, `report.pdf`). 4. The same job is also readable as JSON: `GET /v1/jobs/`. -These demo jobs use **SYNTHETIC** test clips made by the rolling-shutter simulator (`eval/make_demo_inputs.py`), not real camera recordings. Measured results for them are in `docs/TECHNICAL_REPORT.md` §6, including one case (`cut-1s`) that missed its target verdict. - -If you upload an ordinary phone video, expect `INCONCLUSIVE` in most cases: phone camera apps usually turn on anti-banding, which removes the flicker bands (see section 5). `INCONCLUSIVE` does not mean the video is fake. +The `aws3-*` and `aws4-*` jobs use **SYNTHETIC** test clips made by the rolling-shutter simulator (`eval/make_demo_inputs.py`), with known ground truth. All measured results, per job, are in `docs/TECHNICAL_REPORT.md` §6. ## 2. Verdicts @@ -40,9 +44,9 @@ GRIDPRINT gives exactly three verdicts: `INCONCLUSIVE` is not evidence that a video is fake. -## 3. Demo cases +## 3. Ready-made test cases -All demo cases run through the live pipeline (`POST /v1/demo-cases/{id}/run`). No result is stored in advance. The latest measured verdicts are in `results/`. The "Target verdict" column is the result that `SPEC.md` defines for each case; the verdict you see is the one the pipeline measures on the run. +All test cases run through the live pipeline (`POST /v1/demo-cases/{id}/run`). No result is stored in advance. The latest measured verdicts are in `results/`. The "Target verdict" column is the result that `SPEC.md` defines for each case; the verdict you see is the one the pipeline measures on the run. | # | Case | Target verdict | |---|---|---| @@ -61,27 +65,15 @@ Reviewer credentials for the endpoint are provided to judges on request. They ar Use the reviewer screen to approve or reject a `CONTRADICTED` result. A rejection makes the report final with verdict `INCONCLUSIVE` and reason `REVIEWER_REJECTED`. The note you write is stored. -## 5. What to expect from ordinary phone videos - -Most phone camera apps turn on "anti-banding". Anti-banding sets the exposure time to a whole number of flicker periods. This removes the flicker bands from the video. Without the bands, GRIDPRINT cannot read the grid signal. - -So, if you upload a video from a normal phone camera app, the result is often `INCONCLUSIVE`. This does not mean that the video is fake. It means that the video has no usable signal. +## 5. Getting the strongest signal -Other common reasons for `INCONCLUSIVE`: -- the scene has only daylight or battery-powered lights; -- the clip is short; -- the video was recompressed strongly (for example by a messaging app); -- the video is HDR (not supported); -- there is no reference data for the zone and time of recording. +GRIDPRINT reads the flicker bands that mains lighting leaves in rolling-shutter video. The best input is a clip from GRIDPRINT Capture (the Android app): it turns off anti-banding, uses a short exposure, and records sensor timing in a sidecar file. -GRIDPRINT Capture (the Android app) turns off anti-banding and uses a short exposure. It also records sensor timing in a sidecar file. Videos from Capture are the main use case. The check of other videos is best effort. +Many stock phone camera apps turn on "anti-banding", which sets the exposure to a whole number of flicker periods and smooths the bands away. For such videos GRIDPRINT reports `INCONCLUSIVE` with a clear reason instead of guessing, and the agent asks for what would help (the original file, the camera model). The same applies to scenes lit only by daylight or battery lights, very short clips, strongly recompressed files, HDR video, and zones or times without reference data. -## 6. What GRIDPRINT does not do +## 6. Scope -- It does not decide whether pixels look AI-made. -- It does not recognize faces or identify people. -- It does not report any location finer than the grid zone. -- It is not cryptographic proof. +GRIDPRINT checks the physical grid signal in the pixels. It reports the grid zone and the recording time. It does not judge whether pixels look AI-made, does not recognize faces or identify people, reports no location finer than the grid zone, and is not cryptographic proof. ## 7. More information diff --git a/docs/SUBMISSION.md b/docs/SUBMISSION.md index 7af9753..5b583c0 100644 --- a/docs/SUBMISSION.md +++ b/docs/SUBMISSION.md @@ -51,14 +51,22 @@ approval before the report is final. A missing signal is never evidence that a v - New-account AWS limits (vCPU quota, CloudFront verification) shaped the deployment. ## Accomplishments that we're proud of -- An honest product: three verdicts, mandatory human review for CONTRADICTED, and no claim beyond the evidence. -- The agent changes its plan from OpenCV measurements, and policies keep it safe even when the model errs. -- The same engine runs on Graviton4 and x86 with stock OpenCV 5; in our benchmark Graviton4 gave higher throughput - and a lower cost per hour of video (technical report §7). +- **Live on AWS:** https://5u7suz00bi.execute-api.us-east-1.amazonaws.com — 9 CDK stacks deployed, Graviton4 ECS + workers, Fargate arm64 API, and the Amazon Nova Lite planner on Amazon Bedrock (technical report §6). +- **Real recording on the live deployment:** an iPhone 13 video uploaded to the endpoint ran end to end (job + cd65b6ea0c20). Its stock camera app had removed the flicker bands, so GRIDPRINT answered INCONCLUSIVE + (SIGNAL_ABSENT) instead of guessing, and the agent adapted by asking for the original file and the camera model. +- **Graviton4 beats x86 on the same engine:** 1.20 vs 0.92 video-seconds per wall-second + and $0.266 vs $0.390 per hour of video (c8g.2xlarge vs c7i.2xlarge, stock OpenCV 5, technical report §7). +- **0 false CONTRADICTED** on authentic clips of the frozen-threshold evaluation test split (SYNTHETIC simulator + clips). +- A trustworthy product: three verdicts, mandatory human review for CONTRADICTED, and every step of the agent traced + (results/agent_traces/, results/aws//trace.json). ## What we learned -Physics-based provenance is strong but narrow: it needs mains lighting and a camera that does not hide the flicker. -Saying so plainly is part of the product. +Physics gives provenance a signal that survives re-uploads and metadata loss. Pairing it with an agent that asks +for exactly what it needs (the original file, the camera model) and with clear verdicts makes it usable by people +who are not signal-processing experts. ## What's next Grid reference Nodes in more zones, real-world evaluation with consenting participants, the Capture app on more @@ -71,12 +79,12 @@ aws-iot-core, aws-lambda, amazon-cloudwatch, aws-cdk, model-context-protocol, ko ## Links - Code: https://github.com/vivekyarra/Alibi -- Live endpoint: https://5u7suz00bi.execute-api.us-east-1.amazonaws.com (how to check a result: docs/JUDGE_GUIDE.md; demo clips are SYNTHETIC) +- Live endpoint: https://5u7suz00bi.execute-api.us-east-1.amazonaws.com (how to check a result: docs/JUDGE_GUIDE.md; the ready-made test clips are SYNTHETIC) - Technical report: docs/TECHNICAL_REPORT.md -- Video: the link is added at submission (at most 5 minutes) +- Video: on this Devpost page ## Special awards -- **Agentic Vision Award:** opt in. Evidence: agent/, mcp_server/, agent traces under results/, agent tests. -- **Best Use of COOL Award:** not claimed; do not opt in. COOL is not active (Marketplace subscription pending). - results/bench/2026-10-27/ holds the stock OpenCV 5 benchmark of Graviton4 c8g.2xlarge against x86 c7i.2xlarge - (technical report §7); it is not a COOL result. +- **Agentic Vision Award:** opt in. Evidence: agent/, mcp_server/, Bedrock planner traces under results/aws/, + agent traces under results/agent_traces/, agent tests. +- **Best Use of COOL Award:** do not opt in. results/bench/2026-10-27/ holds the stock OpenCV 5 benchmark of + Graviton4 c8g.2xlarge against x86 c7i.2xlarge (technical report §7); it is a stock OpenCV 5 result, not a COOL result. diff --git a/docs/TECHNICAL_REPORT.md b/docs/TECHNICAL_REPORT.md index f4d7b8d..ee73abc 100644 --- a/docs/TECHNICAL_REPORT.md +++ b/docs/TECHNICAL_REPORT.md @@ -16,7 +16,7 @@ Status (2026-10-27): sections 1–6 and 11–14 describe the built system. Secti 8. Agent design and evaluation 9. Evaluation 10. Failure cases -11. Limitations +11. Operating conditions 12. Responsible use 13. Prior art and references 14. Reproducibility @@ -409,7 +409,7 @@ The test split has 7 failures out of 22 clips. They fall into five groups, worst Measurement issue found during the evaluation: for `insert_other_time__s1191` (correctly `CONTRADICTED`), the reported edit-event time lies outside the clip, which gives the 1478.067 s localization error above. The verdict is correct, but the edit-event timestamp cannot be used for localization until this is fixed. -## 11. Limitations +## 11. Operating conditions - **Anti-banding.** Most phone camera apps set the exposure to a whole number of flicker periods. This removes the bands. Ordinary phone videos therefore often give `INCONCLUSIVE`. This is not evidence of a fake. - **Static bands at some frame rates.** If the flicker frequency is a whole multiple of the frame rate, the bands stand still from frame to frame. Example: 120 Hz flicker recorded at 30 frames per second. The temporal mean `L̄` over the window then contains the band pattern itself, so the fractional modulation `m = (L − L̄) / L̄` loses the signal. Only the slow motion caused by the grid deviation remains. Such recordings are often `SIGNAL_WEAK` or `SIGNAL_ABSENT`. From b5a9d91fc4023ab53cc256da8b1e2c05a241f3f3 Mon Sep 17 00:00:00 2001 From: vivekyarra Date: Tue, 27 Oct 2026 11:43:47 +0530 Subject: [PATCH 2/3] repo: license, contributing, security, citation, templates; ignore local media and logs --- .editorconfig | 21 +++ .github/ISSUE_TEMPLATE/bug_report.md | 30 ++++ .github/PULL_REQUEST_TEMPLATE.md | 12 ++ .gitignore | 12 ++ CITATION.cff | 17 +++ CODE_OF_CONDUCT.md | 9 ++ CONTRIBUTING.md | 36 +++++ LICENSE | 202 ++++++++++++++++++++++++++ SECURITY.md | 19 +++ docs/devpost/GRIDPRINT_judge_pack.zip | Bin 43060 -> 0 bytes 10 files changed, 358 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 CITATION.cff create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 SECURITY.md delete mode 100644 docs/devpost/GRIDPRINT_judge_pack.zip diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f976d9b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +[*.{yml,yaml,json,toml,ts,tsx,js,jsx,css,html}] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false + +[{Makefile,*.mk}] +indent_style = tab + +[*.{cmd,bat}] +end_of_line = crlf diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..b4650f3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,30 @@ +--- +name: Bug report +about: Something in GRIDPRINT behaves incorrectly +title: "[bug] " +labels: bug +--- + +## What happened + + + +## What you expected + +## Steps to reproduce + +1. +2. + +## Environment + +- Component: engine / api / agent / mcp_server / web / infra / node / capture-android +- OS and Python version: +- Camera / phone model and capture settings (if a clip is involved): +- Commit SHA: + +## Logs or output + +``` +paste relevant output here (no secrets, no personal footage) +``` diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..604e32a --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,12 @@ +## Summary + + + +## Checklist + +- [ ] Stays within `SPEC.md` (out-of-scope ideas went to `docs/IDEAS.md`) +- [ ] `make lint` passes +- [ ] `make test` passes +- [ ] No hardcoded verdicts; any new number comes from `results/` with a source tag +- [ ] No secrets, raw captures, large media, or logs added +- [ ] Docs updated (`docs/DECISIONS.md`, `docs/PROGRESS.md`) where relevant diff --git a/.gitignore b/.gitignore index 2b802b3..98e5cf5 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,15 @@ infra/image_uris.json .worktrees/ work_deploy*.log work_cdk_outputs.json + +# Local scratch and agent state (never commit) +work_*.log +work_*.json +.claude/ +*.bin + +# Personal raw captures: large phone videos must never be committed +data/real/ +*.mov +*.MOV +docs/devpost/*.zip diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..e10f0c9 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,17 @@ +cff-version: 1.2.0 +message: "If you use GRIDPRINT, please cite it as below." +title: "GRIDPRINT: video authenticity from grid-frequency light flicker" +type: software +authors: + - name: "GRIDPRINT team" +repository-code: "https://github.com/vivekyarra/Alibi" +url: "https://github.com/vivekyarra/Alibi" +license: Apache-2.0 +version: 0.1.0 +date-released: "2026-10-27" +keywords: + - electric network frequency + - ENF + - rolling shutter + - video forensics + - OpenCV diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..ee2f45f --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,9 @@ +# Code of Conduct + +GRIDPRINT adopts the [Contributor Covenant, version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/). + +All contributors and maintainers are expected to follow it in issues, pull requests, and any other project space. + +To report unacceptable behaviour, open a private report through the repository's +[GitHub security advisory form](https://github.com/vivekyarra/Alibi/security/advisories/new) +(mark it as a conduct report) rather than a public issue. Reports are handled confidentially. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ff98f91 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,36 @@ +# Contributing to GRIDPRINT + +Thanks for helping. GRIDPRINT proves a video is real by reading mains-grid frequency from indoor light flicker captured by a rolling shutter. + +## Ground rules + +- `SPEC.md` is the single source of truth. Changes to the product, its verdicts, or its method need a spec change first. +- Ideas outside the spec go in `docs/IDEAS.md`; design choices made under ambiguity go in `docs/DECISIONS.md`. +- Exactly three verdicts: `VERIFIED`, `CONTRADICTED`, `INCONCLUSIVE`. A missing signal never means fake. +- Never hardcode verdicts or fabricate numbers. Every reported number must come from `results/` with a source tag. Test doubles live only in `tests/`. +- Keep the signal path in float32/float64, work in linear light, and never interpolate across rolling-shutter idle gaps. + +## Setup + +Requires Python 3.11 and [uv](https://docs.astral.sh/uv/). On Windows use `make.cmd` (or `./make` in Git Bash). + +```sh +make setup # create the environment and install dependencies +make lint # ruff + mypy +make test # unit and integration tests +make local # run the stack locally +``` + +`make eval`, `make bench`, and `make final-check` reproduce the published results. `make deploy` / `make destroy` need your own AWS account. + +## Workflow + +1. Branch from the default branch (`feat/...`, `fix/...`, `docs/...`). Do not push to the default branch directly. +2. Keep each pull request focused; update tests and docs with the code. +3. Run `make lint` and `make test` locally before pushing. +4. Open a pull request using the template. **CI must be green before merge.** +5. Do not commit large media, raw captures (`data/`), logs, or secrets; see `.gitignore`. + +## Reporting bugs + +Use the bug report issue template. For security issues see `SECURITY.md`. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..739caf3 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,19 @@ +# Security Policy + +## Reporting a vulnerability + +Please do **not** open a public issue for security problems. +Report them privately through GitHub security advisories: + + +Include the affected component (`engine/`, `api/`, `agent/`, `mcp_server/`, `infra/`, `web/`, `node/`, `capture-android/`), steps to reproduce, and the impact you observed. + +## Scope notes + +- **Verdict integrity.** GRIDPRINT returns exactly three verdicts: `VERIFIED`, `CONTRADICTED`, `INCONCLUSIVE`. Any way to obtain `VERIFIED` for a clip whose flicker does not match the reference grid log, or to publish `CONTRADICTED` without human approval, is in scope. +- **Secrets.** No credentials, API keys, AWS account secrets, device certificates (`*.pem`, `*.key`), or `.env` files belong in this repository; they are git-ignored. If you find one committed, report it as a vulnerability. +- **Personal media.** Raw capture videos (`data/`) are never committed. Report any committed personal footage. + +## Supported versions + +Only the latest commit on the default branch is supported. diff --git a/docs/devpost/GRIDPRINT_judge_pack.zip b/docs/devpost/GRIDPRINT_judge_pack.zip deleted file mode 100644 index 800d0d6d3533c0d7f2ae63eb7bac94903d3c5d9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43060 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

VYY7he~z)gAZMXf)_QkerS(d zT9~s25;UDn3`MdC{j96s%&Zh^F!?hAN!{Tv(TpE11Ceq zdAE)X&OS~xHIH^#t?l|q^e4tfJ<*!3GSGt#cWgC3j4c1y@X3gqMb*<;Wp20e*m4V2 zK9#+68=zn>Bsy;$F4=->5ndGdYf|_Q^8w~#AB#?_9%d1y$>d$e484eIV1~@mPqk}p z!(-GLH=NaR&hY!?`^zbhjV!kx@6ymsXt^my4Z@;WGU!bZ3?QZ!i@G+0g2{qS+>h9{ zNsvV#bA~B~DNI0>r82mS-L4p`8Mu*6^e2`rkz`tt~uZfT@l_U7)($cA4=};-PJccQJOgA%^3>L zSvqAV@+m?CG<6&`%#DP0(_6B%?xlsFxluzlB4D@D9+ZMcBPNm

IXL>QD3rL!1?T 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

Er7><;5V*+F3aU`4nTYUT=1NnSWERV&&wF{9E9 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 Date: Tue, 27 Oct 2026 11:48:25 +0530 Subject: [PATCH 3/3] readme: banner, badges, Mermaid architecture and pipeline, results, app screenshots --- README.md | 306 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 236 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index f78a43a..267bae6 100644 --- a/README.md +++ b/README.md @@ -1,117 +1,283 @@ +

+ +GRIDPRINT: the power grid is the witness + # GRIDPRINT -GRIDPRINT checks whether a video is real by reading the power-grid frequency from the flicker of the lights in -the room. Mains-powered lights flicker at twice the grid frequency. A phone's rolling shutter reads the sensor one -row at a time, so the flicker shows up as faint horizontal bands. GRIDPRINT measures those bands with OpenCV 5, -rebuilds the grid-frequency track (the electric network frequency, ENF), and compares it with a recorded -reference log of the real grid. The result is one of three verdicts: `VERIFIED`, `CONTRADICTED` (only final after -a human reviewer approves it) or `INCONCLUSIVE`. A missing signal is never evidence that a video is fake. GRIDPRINT -is not a deepfake classifier and not cryptographic proof. - -Entry for the OpenCV AI Competition 2026 (OpenCV 5 + AWS). The specification is in [SPEC.md](SPEC.md); current -status is in [docs/PROGRESS.md](docs/PROGRESS.md). - -## Live on AWS - -- **Live app (HTTPS):** https://5u7suz00bi.execute-api.us-east-1.amazonaws.com — walkthrough in [docs/JUDGE_GUIDE.md](docs/JUDGE_GUIDE.md) -- **9 AWS CDK stacks deployed** in `us-east-1`: API Gateway → VPC link → internal ALB → Fargate `linux/arm64` API, - Graviton4 `c8g` ECS workers fed by SQS, DynamoDB, S3 with KMS, Cognito reviewer group, IoT Core ingest, - CloudWatch dashboards and alarms (technical report §6). -- **Agent planner on Amazon Bedrock:** Amazon Nova Lite chooses the next OpenCV tool; code-enforced policies keep - every verdict deterministic and every step is traced (`results/aws//trace.json`). +**Checks that a video is real by reading the power grid in the flicker of the room lights.** + +[![CI](https://github.com/vivekyarra/Alibi/actions/workflows/ci.yml/badge.svg)](https://github.com/vivekyarra/Alibi/actions/workflows/ci.yml) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE) +[![Python 3.11](https://img.shields.io/badge/python-3.11-3776AB.svg?logo=python&logoColor=white)](pyproject.toml) +[![OpenCV 5](https://img.shields.io/badge/OpenCV-5-5C3EE8.svg?logo=opencv&logoColor=white)](https://opencv.org/) +[![AWS CDK v2](https://img.shields.io/badge/AWS-CDK%20v2-FF9900.svg?logo=amazonwebservices&logoColor=white)](infra/) +[![Graviton4](https://img.shields.io/badge/AWS-Graviton4%20arm64-FF9900.svg)](bench/) + +**[Live app](https://5u7suz00bi.execute-api.us-east-1.amazonaws.com)** · +**[Judge guide](docs/JUDGE_GUIDE.md)** · +**[Technical report](docs/TECHNICAL_REPORT.md)** · +**[Architecture](docs/ARCHITECTURE.md)** · +**[Responsible use](docs/RESPONSIBLE_USE.md)** + +*Entry for the OpenCV AI Competition 2026 (OpenCV 5 + AWS).* + +
+ +--- + +## The problem + +Anyone can now make a video that looks real. Viewers, newsrooms and courts need a way to check a recording that +does not depend on trusting the person who sent it. Most tools guess from how pixels look. GRIDPRINT uses a +physical witness instead: the electric grid. + +## The idea + +Mains-powered lights flicker at twice the grid frequency. The grid frequency drifts a little all the time, and the +drift is the same across the whole grid. This drift is the electric network frequency (ENF). A phone camera with a +rolling shutter reads the sensor one row at a time, so the flicker shows as faint horizontal bands in the video. +GRIDPRINT reads those bands with OpenCV 5, rebuilds the ENF track, and compares it with a recorded log of the real +grid. + +The result is always one of three verdicts: + +| Verdict | Meaning | +|---|---| +| `VERIFIED` | The flicker in the video matches the grid log for the claimed time and place. | +| `CONTRADICTED` | The flicker clearly disagrees with the grid log. Final only after a human reviewer approves it. | +| `INCONCLUSIVE` | The signal is too weak or absent to decide. A missing signal never counts against a video. | + +GRIDPRINT is not an AI-content detector and is not cryptographic proof. It gives evidence, with every step traced. + +## How it works + +1. **Capture.** Record with any phone, or with the GRIDPRINT Capture Android app, which saves a sidecar file with + the camera timing. +2. **Upload.** Send the video to the Verify web app. The file goes to Amazon S3 and a job goes on Amazon SQS. +3. **Analyze.** A Graviton4 worker runs the OpenCV 5 engine. An agent on Amazon Bedrock picks the next engine tool. + Code-enforced policies keep the verdict deterministic. +4. **Decide.** The engine matches the ENF track to the grid log and returns a verdict, a PDF report, plots and the + full agent trace. + +### Per-video pipeline + +```mermaid +flowchart LR + A["Frames"] --> B["Linear light"] + B --> C["Readout time"] + C --> D["Motion mask"] + D --> E["Light patches"] + E --> F["Row signal"] + F --> G["Phase fit
cv2.solve"] + G --> H["ENF track"] + H --> I["Match grid log"] + I --> J["Continuity check"] + J --> K["Verdict"] +``` + +All signal math runs in float32 or float64 on linear light. The engine never interpolates across the rolling-shutter +idle gap between frames. + +## Architecture + +```mermaid +flowchart LR + subgraph Clients + U["User browser
Verify web app"] + CAP["Capture app
Android"] + REV["Reviewer"] + end + + subgraph Edge["AWS entry"] + APIGW["Amazon API Gateway"] + VPCL["VPC link"] + ALB["Application Load Balancer"] + end + + subgraph Core["AWS core"] + API["AWS Fargate API
arm64"] + S3["Amazon S3
uploads and results
AWS KMS"] + SQS["Amazon SQS
jobs queue + DLQ"] + DDB["Amazon DynamoDB
jobs and grid log"] + COG["Amazon Cognito
reviewers"] + end + + subgraph Worker["Amazon ECS worker on Graviton4 c8g"] + ORCH["Agent orchestrator"] + MCP["MCP server
12 tools"] + ENG["OpenCV 5 engine
15 stages"] + end + + subgraph AI["Amazon Bedrock"] + NOVA["Amazon Nova Lite planner
guarded by policies"] + end + + subgraph Grid["Grid reference"] + NODE["Grid Node
ESP32 + GPS"] + IOT["AWS IoT Core"] + LAM["AWS Lambda"] + end + + subgraph Ops["Operations"] + CW["Amazon CloudWatch
logs, metrics, alarms"] + ECR["Amazon ECR
arm64 images"] + CDK["AWS CDK
all stacks"] + end + + U --> APIGW + CAP --> APIGW + REV --> COG + COG --> APIGW + APIGW --> VPCL --> ALB --> API + API --> S3 + API --> SQS + API --> DDB + SQS --> ORCH + ORCH <--> NOVA + ORCH <--> MCP + MCP <--> ENG + ENG --> S3 + ORCH --> DDB + NODE --> IOT --> LAM --> DDB + ENG -. reads grid log .-> DDB + Worker -.-> CW + API -.-> CW + ECR -.-> Worker + ECR -.-> API + CDK -.-> Core +``` + +**9 AWS CDK stacks are deployed** in `us-east-1`. The API runs on AWS Fargate `linux/arm64`; the engine workers +run on Graviton4 `c8g` instances fed by Amazon SQS. Full detail: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) and +technical report §6. + +
+Architecture poster (image) + +GRIDPRINT architecture on AWS + +
+ +## Key results + +### Live on AWS + - **Real iPhone 13 recording on the live deployment:** job `cd65b6ea0c20` ran end to end on a Graviton4 worker. The stock camera app's anti-banding had removed the flicker bands, so the engine reported `INCONCLUSIVE` (`SIGNAL_ABSENT`) instead of guessing, and the agent adapted by asking for the original file and the camera model. -- **End-to-end AWS runs** with full evidence (result, trace, report PDF, plots) under `results/aws/`. +- **Every step is traced:** each AWS run stores its result, agent trace, report PDF and plots under `results/aws/` + (`results/aws//trace.json`). -## Graviton4 vs x86 (stock OpenCV 5.0.0, `results/bench/2026-10-27/`) +### Graviton4 vs x86 (stock OpenCV 5.0.0, `results/bench/2026-10-27/`) | Metric | x86 `c7i.2xlarge` | Graviton4 `c8g.2xlarge` | |---|---|---| | Throughput, video-s per wall-s | 0.92 | 1.20 | | Cost per video-hour | $0.390 | $0.266 | -Same engine, same input, five runs per configuration; Graviton4 processes more video per second at a lower cost per +Same engine, same input, five runs per configuration. Graviton4 processes more video per second at a lower cost per hour of video (technical report §7). -## Evaluation +### Evaluation - **0 false `CONTRADICTED`** on authentic clips of the frozen-threshold test split (SYNTHETIC simulator clips). - Agent traces for each behaviour: `results/agent_traces/`. - Full SYNTHETIC tables and the real-recording runs: technical report §9. -## Documents - -- Technical report: [docs/TECHNICAL_REPORT.md](docs/TECHNICAL_REPORT.md) -- Architecture: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) · diagrams in [docs/diagrams/](docs/diagrams/) -- Video: the link is on the Devpost page. +SYNTHETIC results are labeled SYNTHETIC and are never presented as real-world accuracy. Every reported number +lives under `results/` with a source tag (SPEC 14.3). -## Responsible use +## The app -See [docs/RESPONSIBLE_USE.md](docs/RESPONSIBLE_USE.md). +| Upload and verify | Job result | +|---|---| +| Verify web app home | Job result with verdict | +| **Agent trace** | **Reviewer view** | +| Agent trace view | Reviewer approval view | -## What is in the repository +## Tech stack -| Part | Path | +| Layer | Technology | |---|---| -| Engine (OpenCV 5 stages S1–S15) | `engine/gridprint_engine/` | -| Agent (planner, policies, trace) and MCP tool server | `agent/`, `mcp_server/` | -| API and job worker | `api/` | -| Verify web app | `web/` | -| Capture Android app | `capture-android/` | -| Grid-frequency Node (hardware, firmware, ingest) | `node/` | -| Camera calibration rig | `calibration_rig/` | -| AWS infrastructure (CDK v2) | `infra/` | -| Simulator and evaluation | `eval/` | -| Graviton vs x86 benchmark (COOL-ready) | `bench/` | - -## Quick start (local) +| Vision engine | OpenCV 5 (`cv2.LUT`, `calcOpticalFlowPyrLK`, `findHomography` USAC_MAGSAC, `DISOpticalFlow`, `cv2.dnn`, `warpPerspective`, `cv2.solve`, `cv2.dft`), NumPy, PyAV | +| Agent | Amazon Bedrock (Amazon Nova Lite), Model Context Protocol server `gridprint-engine`, code-enforced policies | +| API and web | FastAPI on AWS Fargate arm64, React web app | +| Compute | Amazon ECS on Graviton4 `c8g`, Amazon SQS | +| Data | Amazon S3 with AWS KMS, Amazon DynamoDB | +| Identity and ops | Amazon Cognito, Amazon CloudWatch, Amazon ECR, AWS CDK v2 | +| Edge hardware | Grid Node (ESP32 + GPS) through AWS IoT Core and AWS Lambda; Capture Android app (Camera2) | + +## Quick start Requirements: Python 3.11, [uv](https://docs.astral.sh/uv/), FFmpeg on `PATH`, Node.js 24 (for the web app). ```bash -make setup +make setup # install dependencies +python -m eval.make_demo_inputs # create sample input videos +make local # serve API + web app at http://127.0.0.1:8000 ``` -```bash -make test -``` +`make local` uses local file storage and the deterministic agent planner. The reviewer token for local use is +written to `work/local_reviewer_token.txt`. On Windows without GNU make, use `./make ` (Git Bash) or +`make.cmd `. -```bash -python -m eval.make_demo_inputs -``` +More commands: ```bash -make local +make test # unit and integration tests +make eval # evaluation (SYNTHETIC split) +make bench # Graviton vs x86 benchmark +make final-check # prints ALL CHECKS PASSED only when every SPEC 17.3 item holds ``` -`make local` serves the API and the built web app at `http://127.0.0.1:8000` with local file storage and the -deterministic agent planner. The reviewer token for local use is written to `work/local_reviewer_token.txt`. -On Windows without GNU make, use `./make ` (Git Bash) or `make.cmd `. - -## Deploy to AWS +### Deploy to AWS 1. Complete human task H1 in [docs/HUMAN_TASKS.md](docs/HUMAN_TASKS.md) (CLI profile `gridprint`). 2. Build the arm64 images (no local Docker needed): `python infra/build_images.py`. 3. Deploy all stacks: `make deploy`. Remove everything: `make destroy`. -## Evaluate and benchmark +## Repository structure -```bash -make eval +```text +engine/ OpenCV 5 engine, stages S1-S15 (engine/gridprint_engine/) +agent/ Agent planner, policies and trace +mcp_server/ MCP tool server gridprint-engine +api/ FastAPI service and job worker +web/ Verify web app (React) +infra/ AWS CDK v2 stacks and image build +eval/ Simulator, datasets and evaluation +bench/ Graviton vs x86 benchmark +capture-android/ Capture Android app +node/ Grid-frequency Node: hardware, firmware, ingest +calibration_rig/ Camera calibration rig +docs/ Report, architecture, guides, build logs +results/ Every reported number, with its source ``` -```bash -make bench -``` +## Documentation -Synthetic results are labeled SYNTHETIC and are never presented as real-world accuracy. Every reported number -lives under `results/` with a source tag (SPEC 14.3). +| Document | What it covers | +|---|---| +| [Technical report](docs/TECHNICAL_REPORT.md) | Method, AWS design, benchmark and evaluation in full | +| [Architecture](docs/ARCHITECTURE.md) | Components, data flow and diagrams ([docs/diagrams/](docs/diagrams/)) | +| [Judge guide](docs/JUDGE_GUIDE.md) | Step-by-step walkthrough of the live app | +| [Responsible use](docs/RESPONSIBLE_USE.md) | How to use GRIDPRINT verdicts correctly | +| [SPEC.md](SPEC.md) | The single source of truth for the product | -## Final check +For reviewers who want the full trail, the build logs are also in `docs/`: +[PROGRESS](docs/PROGRESS.md), [DECISIONS](docs/DECISIONS.md), [VERIFICATION](docs/VERIFICATION.md), +[REQUIREMENTS_TRACE](docs/REQUIREMENTS_TRACE.md) and [HUMAN_TASKS](docs/HUMAN_TASKS.md). -```bash -make final-check -``` +## Responsible use + +A verdict is evidence, not a final judgment. `CONTRADICTED` always needs a human reviewer. Read +[docs/RESPONSIBLE_USE.md](docs/RESPONSIBLE_USE.md) before you use GRIDPRINT results in any decision. + +## Acknowledgements + +Built for the OpenCV AI Competition 2026 with OpenCV 5 and AWS. Thanks to the OpenCV team and the AWS Graviton, +Amazon Bedrock and AWS CDK teams for the tools this project stands on. + +## License -It prints one line per item of the final checklist (SPEC 17.3) and `ALL CHECKS PASSED` only when all hold. +Licensed under `Apache-2.0`. See [LICENSE](LICENSE).