#!/usr/bin/env python3
"""Build an honest, synthetic proof package using the installed product compiler.

Only writes the requested output directory and disposable Docker container state.
Never edits the product repository or rewrites its emitted native node parameters.
"""
from __future__ import annotations

import argparse
import asyncio
import copy
import hashlib
import json
import re
import shutil
import subprocess
import sys
import uuid
from datetime import datetime, timezone
from pathlib import Path

sys.dont_write_bytecode = True
IMAGE = "docker.n8n.io/n8nio/n8n:2.39.10"
ROOT = Path(__file__).resolve().parent
EXAMPLE = "invoice-approval-01"

SOURCE_NOTES = """# Northstar Office Supply: invoice intake

**Synthetic demonstration data.** Northstar Office Supply, its invoices, source
records, and decision are fictional. They are authored fixtures, not customer
evidence, recorded interviews, or an observed business outcome.

## Source PS-SRC-01: fictional walkthrough notes

The intake team receives a USD invoice and looks for an accountable team. Missing
owners cause the invoice to return for clarification. Owned invoices over $1,000
need manager review; other owned invoices enter standard review. No invoice is
approved or paid by this example.

## Source PS-SRC-02: fictional invoice-review policy

Rule **PS-RULE-01**, version 1:

1. A missing, empty, or whitespace-only owner means `needs_information`.
2. Otherwise an amount strictly greater than USD 1,000 means `manager_review`.
3. Otherwise the result is `standard_review`.

The USD 1,000 boundary belongs to standard review. Missing owner takes precedence
over amount. Inputs in this bounded example have valid nonnegative numeric amounts
in USD. Currency conversion and malformed amount handling are outside its scope.

## Source PS-SRC-03: fictional invoice export

| Invoice | Amount (USD) | Owner | Expected queue |
| --- | ---: | --- | --- |
| INV-101 | 750 | Operations | standard_review |
| INV-102 | 1,250 | Operations | manager_review |
| INV-103 | 1,250 | empty | needs_information |
| INV-104 | 1,000 | Operations | standard_review |
| INV-105 | 750 | three spaces | needs_information |

The machine-readable export is `invoices.json`. These expected results are test
oracles, kept separate from the runtime's actual output in `run-result.json`.

## Traceability boundary

PS-SRC-01 and PS-SRC-02 support PS-RULE-01; PS-SRC-03 supplies five acceptance
cases. `decision.md` explains the selected design. The IR was manually authored
from that rule. The existing ProcessScout compiler generated the native workflow.
This is not a recording of automatic evidence extraction or a product UI journey.
"""

DECISION = """# Decision PS-DEC-01: evaluate a review queue

**Synthetic demonstration decision; no human approval or production release is
claimed.** Business: Northstar Office Supply. Rule: PS-RULE-01, version 1.

The fictional evidence suggests two options:

| Option | Benefit | Remaining responsibility |
| --- | --- | --- |
| Add an owner checklist to invoice intake | Clarifies the procedure without automation | A person checks every invoice and assigns the queue |
| Evaluate the owner and amount rule in a workflow | Makes the queue rule executable and testable | A person still supplies complete data and reviews the invoice |

This demonstration selects the second option only to test native workflow
generation and execution. It does not establish an automation recommendation for
a real business, commercial value, or a customer's authorization.

The output is a queue label, never an approval or payment. The intended sequence
is a scheduled trigger, synthetic fixture input, then rule evaluation. Five
parallel fixture paths exercise the same rule, including the threshold and
missing-owner exceptions. No HTTP requests, credentials, messages, accounting
updates, or money movement are included.

The primary IR expresses PS-RULE-01 as a `data.transform` expression. The separate
`branch-diagnostic` artifact probes the compiler's `condition.branch` mapping for
the amount threshold only; it is not the full owner rule. Both preserve actual
compiler output without native JSON repairs.

## Acceptance criteria and release boundary

All five native outputs must contain the expected invoice ID and review queue.
An n8n process exit code or engine status of `success` alone does not satisfy this
criterion. See the actual result in `validation.json` and `run-result.json`.

The CLI harness adds a Manual Trigger and copies the generated schedule trigger's
outgoing connections. It preserves every generated node and existing connection.
This allows isolated execution of the fixture/rule paths; scheduled delivery is
not tested. The generated native artifact and the test harness are separate files.

Product fidelity findings and approval requirements are retained. No approval
record, product runtime deployment, live account, production activation, or
scheduled delivery is represented by this test.
"""

README = """# Reproduce the invoice workflow proof

This package contains a real compiler export and a real isolated n8n CLI run with
fictional input data. Read `manifest.json` and `run-result.json` for the result;
an engine `success` status must not be confused with a passing business rule.

## Replay the included native workflow

Docker and Python's standard library are enough to replay the included workflow.
No ProcessScout account, product repository or source access is needed:

```sh
docker pull docker.n8n.io/n8nio/n8n:2.39.10
python run-sandbox.py --output replay-result
```

This imports the published `sandbox-harness.json` into a disposable offline n8n
container, captures real output, and checks all five invoice results. It exits 0
only when the engine completes and the expected business-rule outputs match.
It does not rebuild the native artifact from Workflow IR.

## Rebuild with licensed product source access

- An authorized local checkout of the ProcessScout product repository
  (`betterwrk-discover`) at the commit recorded in `manifest.json`.
- That product's Python environment, with its dependencies installed.
- Docker with the n8n 2.39.10 image available.

No product code changes, product API calls, external credentials, live accounts,
or production services are needed. The product checkout is only imported/read;
Python bytecode writes are disabled. Every run creates a disposable container
with `--network none`, no published ports, dropped Linux capabilities, and a
read-only mount of this package. The container's generated local n8n database,
local account identifiers, and encryption configuration disappear on removal.

## Rebuild command

From the downloaded package directory, use your product Python interpreter:

```sh
docker pull docker.n8n.io/n8nio/n8n:2.39.10
python reproduce.py --product-repo path/to/betterwrk-discover --output rerun
```

The source version of the script is named `generate.py`; the downloadable copy is
`reproduce.py`. Both perform the same work. The command exits 0 when the proof
package was captured successfully, even when the recorded workflow acceptance
result is `failed`. Inspect the printed `business_rule_status`, and do not infer
workflow correctness from the generator's process exit code.

The generation steps are: validate the authored IR with `WorkflowIRDocument`,
call the real `N8nWorkflowCompiler.analyze` and `.compile`, aggregate findings
with `build_fidelity_report`, preserve the compiler content, add a separate CLI
harness, and invoke `n8n import:workflow` followed by `n8n execute --rawOutput`.
The harness skips the generated schedule trigger. All generated node parameters
and preexisting connections are unchanged. No compiler result is hand-repaired.

Runtime output is parsed separately from expected fixture results. The CLI can
report success for a workflow that produces the wrong output. Acceptance requires
matching both the invoice ID and queue for every fixture. Timestamp fields,
ephemeral IDs and execution timings vary across runs; native artifact digests are
deterministic for the same fixture and compiler source.

## Inspect

- `source-notes.md` → `decision.md` → `workflow-ir.json` → `n8n-workflow.json`
- `compiler-artifact.json`: compiler metadata and canonical digests
- `fidelity-report.json`: unaltered product fidelity report
- `validation.json`: schema, fidelity, harness, and observed acceptance checks
- `run-result.json`: expected versus actual outputs
- `sandbox-harness.json`: the exact imported CLI test wrapper
- `n8n-execution.json` and `n8n-cli.log`: actual local runtime evidence
- `branch-diagnostic-*`: a separate amount-only IF mapping diagnostic
- `runtime-environment.json`: image ID, digest, commands and limits
- `SHA256SUMS`: file integrity hashes (not a signature or attestation)

Verify downloaded file integrity with:

```sh
shasum -a 256 -c SHA256SUMS
```

Synthetic evidence and authored IR are identified as such. The actual compiler
and local engine execution are real. This package does not claim automated
evidence extraction, human signoff, product deployment, a working scheduled
integration, or a production outcome. No credentials were provided or needed.
"""


def dump(path, value):
    path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n")


def sha(path):
    return hashlib.sha256(path.read_bytes()).hexdigest()


def command(args, *, timeout=180):
    return subprocess.run(args, text=True, capture_output=True, timeout=timeout, check=False)


def build_ir(fixture, *, branch=False):
    doc = {
        "schema_version": "1.0",
        "identity": {"name": "Northstar invoice review queues" if not branch else "Northstar amount-branch diagnostic",
                     "description": "Synthetic PS-RULE-01 proof; authored IR; no payment or approval."},
        "interface": {"inputs": [{"name": "amount", "schema": {"type": "number"}},
                                  {"name": "owner", "schema": {"type": "string"}}],
                      "outputs": [{"name": "invoice_id", "schema": {"type": "string"}},
                                  {"name": "queue", "schema": {"type": "string"}}]},
        "triggers": [{"id": "trigger-schedule", "operation": "schedule.run", "config": {"rule": {"interval": [{"field": "hours", "hoursInterval": 24}]}}}],
        "steps": [], "edges": [], "controls": {"timeouts": [{"scope": "workflow", "seconds": 60}]},
        "data_contracts": [], "credential_requirements": [], "runtime_requirements": [], "native_extensions": {},
    }
    for case in fixture["cases"]:
        suffix = case["id"]
        seed, rule = "input-" + suffix, "rule-" + suffix
        doc["steps"].append({"id": seed, "operation": "data.transform", "mappings": {"invoice_id": suffix, "amount": case["amount"], "owner": case["owner"]}})
        doc["edges"].extend([{"id": "edge-trigger-" + suffix, "source_id": "trigger-schedule", "target_id": seed},
                             {"id": "edge-input-" + suffix, "source_id": seed, "target_id": rule}])
        if branch:
            doc["steps"].append({"id": rule, "operation": "condition.branch", "config": {"operator": "larger", "right": fixture["threshold"]}, "mappings": {"left": "={{ $json.amount }}"}})
            for index, status in enumerate(["manager_review", "standard_review"]):
                output = status + "-" + suffix
                doc["steps"].append({"id": output, "operation": "data.transform", "mappings": {"invoice_id": "={{ $json.invoice_id }}", "queue": status}})
                doc["edges"].append({"id": "edge-" + output, "source_id": rule, "target_id": output, "condition": {"output_index": index}})
        else:
            doc["steps"].append({"id": rule, "operation": "data.transform", "mappings": {
                "invoice_id": "={{ $json.invoice_id }}", "rule_id": fixture["rule_id"],
                "queue": "={{ !$json.owner || !$json.owner.trim() ? 'needs_information' : ($json.amount > " + str(fixture["threshold"]) + " ? 'manager_review' : 'standard_review') }}"}})
    return doc


def harness_for(content, native_id):
    result = copy.deepcopy(content)
    result["id"] = native_id
    result["nodes"].append({"id": "harness-manual-entry", "name": "harness-manual-entry", "type": "n8n-nodes-base.manualTrigger", "typeVersion": 1, "position": [-240, 0], "parameters": {}})
    result["connections"]["harness-manual-entry"] = copy.deepcopy(content["connections"]["trigger-schedule"])
    assert result["nodes"][:-1] == content["nodes"]
    assert {k: v for k, v in result["connections"].items() if k != "harness-manual-entry"} == content["connections"]
    return result


def decode_execution(output):
    decoder = json.JSONDecoder()
    for match in re.finditer(r"(?m)^\{", output):
        try:
            value, _ = decoder.raw_decode(output[match.start():])
        except json.JSONDecodeError:
            continue
        if isinstance(value, dict) and isinstance(value.get("data", {}).get("resultData"), dict):
            return value
    return None


def node_outputs(execution, node):
    if execution is None:
        return []
    runs = execution["data"]["resultData"].get("runData", {}).get(node, [])
    return [item.get("json") for run in runs for stream in run.get("data", {}).get("main", []) for item in (stream or [])]


def run_native(out, harness_name, native_id, image):
    container = "processscout-invoice-proof-" + uuid.uuid4().hex[:12]
    args = ["docker", "run", "--rm", "--network", "none", "--name", container,
            "--cap-drop", "ALL", "--security-opt", "no-new-privileges", "--memory", "2g", "--cpus", "2",
            "-e", "N8N_DIAGNOSTICS_ENABLED=false", "-e", "N8N_VERSION_NOTIFICATIONS_ENABLED=false",
            "-e", "N8N_TEMPLATES_ENABLED=false", "-v", str(out) + ":/proof:ro", "--entrypoint", "sh", image,
            "-c", f"n8n import:workflow --input=/proof/{harness_name} && n8n execute --id={native_id} --rawOutput"]
    started = datetime.now(timezone.utc).isoformat()
    try:
        result = command(args)
        output = result.stdout + result.stderr
        exit_code = result.returncode
    except subprocess.TimeoutExpired as error:
        command(["docker", "rm", "-f", container], timeout=30)
        output = str(error.stdout or "") + str(error.stderr or "") + "\nHarness timed out.\n"
        exit_code = 124
    output = output.replace(str(out), "<proof-package>")
    displayed_args = [item.replace(str(out), "<proof-package>").replace(container, "<disposable-container>") for item in args]
    return {"started_at": started, "completed_at": datetime.now(timezone.utc).isoformat(),
            "process_exit_code": exit_code, "command": displayed_args}, output, decode_execution(output)


async def generate(args):
    repo, out = args.product_repo.resolve(), args.output.resolve()
    if out == repo or repo in out.parents:
        raise SystemExit("Output must be outside the read-only product repository.")
    sys.path.insert(0, str(repo / "backend"))
    bundled_manifest = ROOT / "manifest.json"
    if bundled_manifest.exists():
        previous = json.loads(bundled_manifest.read_text()).get("provenance", {})
        args.product_pr_url = args.product_pr_url or previous.get("product_pr_url")
        args.product_base_commit = args.product_base_commit or previous.get("product_base_commit")
    from services.workflow_ir.canonical_json import canonical_digest
    from services.workflow_ir.contracts import WorkflowIRDocument
    from services.workflow_ir.fidelity import build_fidelity_report
    from services.workflow_ir.provider_registry import AutomationTargetBinding
    from services.workflow_ir.providers.n8n.compiler import N8nWorkflowCompiler

    fixture_path = ROOT / "fixtures" / "invoices.json"
    if not fixture_path.exists():
        fixture_path = ROOT / "invoices.json"
    fixture = json.loads(fixture_path.read_text())
    out.mkdir(parents=True, exist_ok=True)
    digest_info = command(["docker", "image", "inspect", IMAGE, "--format", "{{json .}}"])
    if digest_info.returncode:
        raise SystemExit("Required n8n image unavailable. Pull the documented image explicitly before running.")
    image_info = json.loads(digest_info.stdout)
    # Execute the exact locally inspected image, not a mutable tag resolved later.
    image_id = image_info["Id"]
    commit = command(["git", "-C", str(repo), "rev-parse", "HEAD"]).stdout.strip()
    files = ["backend/services/workflow_ir/providers/n8n/compiler.py", "backend/services/workflow_ir/providers/n8n/semantic_map.py",
             "backend/services/workflow_ir/semantic.py", "backend/services/workflow_ir/contracts.py",
             "backend/services/workflow_ir/fidelity.py", "backend/services/workflow_ir/provider_registry.py"]
    source_status = command(["git", "-C", str(repo), "status", "--porcelain", "--", *files]).stdout.strip()
    source_hashes = {path: sha(repo / path) for path in files}
    version_run = command(["docker", "run", "--rm", "--network", "none", image_id, "--version"], timeout=60)
    version = version_run.stdout.strip().splitlines()[-1]
    if version != "2.39.10":
        raise SystemExit("Unexpected n8n version: " + version)
    compiler = N8nWorkflowCompiler()
    binding = AutomationTargetBinding(provider="n8n", connection_id="synthetic-local-cli")
    reports, artifacts, runs, executions = {}, {}, {}, {}
    for kind in ["primary", "branch"]:
        prefix = "" if kind == "primary" else "branch-diagnostic-"
        doc = WorkflowIRDocument.model_validate(build_ir(fixture, branch=kind == "branch"))
        artifact = await compiler.compile(doc, binding)
        findings = await compiler.analyze(doc, binding)
        report = build_fidelity_report(source_provider="authored-synthetic-ir", target_provider="n8n",
            source_digest=canonical_digest(fixture), workflow_ir_digest=canonical_digest(doc.model_dump(mode="json")),
            artifact_digest=artifact.digest, compiler_version=artifact.compiler_version, findings=findings)
        native_id = "psInvoiceProof01" if kind == "primary" else "psBranchProof01"
        harness = harness_for(artifact.content, native_id)
        dump(out / (prefix + "workflow-ir.json"), doc.model_dump(mode="json"))
        dump(out / (prefix + "n8n-workflow.json"), artifact.content)
        dump(out / (prefix + "compiler-artifact.json"), artifact.model_dump(mode="json"))
        dump(out / (prefix + "fidelity-report.json"), report.model_dump(mode="json"))
        dump(out / (prefix + "sandbox-harness.json"), harness)
        run, output, execution = run_native(out, prefix + "sandbox-harness.json", native_id, image_id)
        (out / (prefix + "n8n-cli.log")).write_text(output)
        dump(out / (prefix + "n8n-execution.json"), execution)
        reports[kind], artifacts[kind], runs[kind], executions[kind] = report, artifact, run, execution

    cases = []
    for item in fixture["cases"]:
        actual = node_outputs(executions["primary"], "rule-" + item["id"])
        expected = {"invoice_id": item["id"], "queue": item["expected"]}
        passed = len(actual) == 1 and all(actual[0].get(k) == v for k, v in expected.items())
        cases.append({"id": item["id"], "input": {"amount": item["amount"], "currency": fixture["currency"], "owner": item["owner"]},
                      "expected": expected, "actual": actual, "status": "passed" if passed else "failed",
                      "fixture_node_output": node_outputs(executions["primary"], "input-" + item["id"])})
    passed_count = sum(case["status"] == "passed" for case in cases)
    business_status = "passed" if passed_count == len(cases) else "failed"
    engine_status = executions["primary"].get("status") if executions["primary"] else "no_execution_record"
    summary = f"{passed_count}/{len(cases)} queue assertions passed. Native n8n engine status: {engine_status}."
    if business_status == "failed":
        summary += " The expected invoice ID and queue were not produced; this is not a successful workflow demonstration."
    limitations = [
        "Synthetic business, evidence, decision and inputs; the Workflow IR was manually authored.",
        "Native artifact produced by the real product compiler; no generated node was repaired.",
        "The separate CLI harness adds a Manual Trigger and bypasses scheduled delivery.",
        "No human approval, product runtime deployment, external credentials, live integration or production release was tested.",
        "The product fidelity report requires approval for approximated description/interface mappings.",
        "The separate branch diagnostic checks the amount condition only; the main workflow checks the full owner and amount rule.",
    ]
    proposed_fix = bool(args.product_pr_url or source_status)
    if proposed_fix:
        limitations.insert(0, "This run uses a proposed product compiler fix. It is pending product review and merge, not a claim about the released product.")
    findings = [
        {"label": "Workflow IR schema", "status": "passed", "detail": "Validated with the product WorkflowIRDocument contract."},
        {"label": "Product compiler fidelity", "status": reports["primary"].disposition.value,
         "detail": "Unaltered product report: " + json.dumps(reports["primary"].model_dump(mode="json")["counts"])},
        {"label": "Credentials", "status": "not_required", "detail": "Zero credential requirements; no external request nodes."},
        {"label": "Native output integrity", "status": "passed", "detail": "Export equals compiler.content. CLI harness preserves every generated node and existing connection."},
        {"label": "n8n engine execution", "status": engine_status, "detail": "Actual local CLI execution; engine completion alone does not establish correct business behavior."},
        {"label": "Business-rule acceptance", "status": business_status, "detail": summary},
        {"label": "Production release", "status": "not_tested", "detail": "No product deployment, human approval record, external integration or live schedule was exercised."},
    ]
    branch_observed = []
    for item in fixture["cases"]:
        executed = executions["branch"]["data"]["resultData"].get("runData", {}) if executions["branch"] else {}
        expected_branch = "manager_review" if item["amount"] > fixture["threshold"] else "standard_review"
        observed_nodes = [name for name in ["manager_review-" + item["id"], "standard_review-" + item["id"]] if name in executed]
        outputs = node_outputs(executions["branch"], expected_branch + "-" + item["id"])
        branch_passed = observed_nodes == [expected_branch + "-" + item["id"]] and outputs == [{"invoice_id": item["id"], "queue": expected_branch}]
        branch_observed.append({"invoice_id": item["id"], "intended_amount": item["amount"],
            "expected_amount_branch": expected_branch, "observed_branch_nodes": observed_nodes,
            "status": "passed" if branch_passed else "failed", "actual_output": outputs,
            "actual_input_from_fixture": node_outputs(executions["branch"], "input-" + item["id"])})
    branch_status = "passed" if all(case["status"] == "passed" for case in branch_observed) else "failed"
    findings.insert(-1, {"label": "Native IF amount diagnostic", "status": branch_status,
                        "detail": str(sum(case["status"] == "passed" for case in branch_observed)) + "/5 amount-branch cases passed, including the USD 1,000 boundary."})
    validation = {"status": business_status, "summary": summary, "findings": findings,
                  "fidelity_report": "fidelity-report.json", "acceptance_rule": "Every output must contain the expected invoice_id and queue.",
                  "branch_diagnostic": {"status": branch_status, "scope": "Amount condition only; not the full owner rule", "observations": branch_observed}}
    execution_result = {"status": business_status, "summary": summary, "engine_status": engine_status,
                        "cases": cases, "limitations": limitations, "run": runs["primary"],
                        "raw_evidence": ["n8n-cli.log", "n8n-execution.json"], "branch_diagnostic_run": runs["branch"]}
    dump(out / "validation.json", validation)
    dump(out / "run-result.json", execution_result)
    dump(out / "invoices.json", fixture)
    (out / "source-notes.md").write_text(SOURCE_NOTES)
    (out / "decision.md").write_text(DECISION)
    patch_sha = None
    if args.product_base_commit:
        patch = command(["git", "-C", str(repo), "diff", args.product_base_commit, "--", "backend/services/workflow_ir", "backend/tests/unit/workflow_ir", "backend/tests/integration/workflow_ir", "backend/tests/fixtures/workflow_ir/n8n"])
        if patch.returncode:
            raise SystemExit("Could not capture product source diff from the specified base commit.")
        patch_sha = hashlib.sha256(patch.stdout.encode()).hexdigest()
        if args.patch_output:
            patch_target = args.patch_output.resolve()
            if patch_target == out or out in patch_target.parents:
                raise SystemExit("Private product source diff must remain outside the public output directory.")
            patch_target.write_text(patch.stdout)
    review_note = ""
    if proposed_fix:
        review_note = "**Proposed product fix, pending merge.** This recorded run uses compiler source at commit `" + commit + "`"
        review_note += (". The product PR reference in manifest.json requires private source access." if args.product_pr_url else "; the pull request is being prepared.")
        review_note += " The unmodified compiler baseline is retained in `baseline/` when included. No released-product or production-deployment claim is made.\n\n"
    (out / "README.md").write_text(review_note + README)
    shutil.copyfile(Path(__file__), out / "reproduce.py")
    shutil.copyfile(ROOT / "run-sandbox.py", out / "run-sandbox.py")
    dump(out / "runtime-environment.json", {"n8n_version": version, "docker_image": IMAGE,
        "executed_image_id": image_id, "repository_digests": image_info.get("RepoDigests", []),
        "network": "none", "published_ports": [], "host_mount": "proof package, read-only",
        "product_compiler_files": source_hashes, "compiler_files_modified_in_worktree": bool(source_status),
        "runs": runs, "log_sanitization": "Host proof-directory path replaced if present; all runtime data originates in disposable synthetic containers."})
    names = [
        ("source", "Source notes", "source-notes.md", "Fictional walkthrough, policy and invoice export."),
        ("decision", "Decision", "decision.md", "Authored option comparison and acceptance criteria."),
        ("ir", "Workflow IR", "workflow-ir.json", "Manually authored canonical input validated by the product contract."),
        ("artifact", "Generated n8n workflow", "n8n-workflow.json", "Untouched native content emitted by the existing compiler."),
        ("validation", "Validation result", "validation.json", "Separate schema, fidelity and real behavior checks."),
        ("fidelity", "Product fidelity report", "fidelity-report.json", "Actual product mapping findings and approval-required disposition."),
        ("run", "Actual sandbox result", "run-result.json", "Five expected-versus-actual cases; engine success is not acceptance success."),
        ("raw", "Raw native execution", "n8n-execution.json", "Actual isolated n8n execution record."),
        ("log", "CLI execution log", "n8n-cli.log", "Captured runtime output with only host-directory redaction if needed."),
        ("harness", "CLI harness", "sandbox-harness.json", "Explicit manual entry wrapper; generated nodes remain unchanged."),
        ("branch", "IF mapping diagnostic", "branch-diagnostic-n8n-workflow.json", "Separate untouched amount-branch compiler probe."),
        ("branch-raw", "IF diagnostic execution", "branch-diagnostic-n8n-execution.json", "Actual branch diagnostic record; compare with the pre-fix baseline."),
        ("environment", "Runtime provenance", "runtime-environment.json", "Image digest, source hashes and commands."),
        ("fixtures", "Synthetic invoices", "invoices.json", "Five acceptance inputs with separate expected queue values."),
        ("readme", "Reproduction instructions", "README.md", "Dependencies, commands, evidence boundaries and integrity verification."),
        ("script", "Reproduction script", "reproduce.py", "Imports the actual compiler and repeats the isolated run."),
        ("replay", "Public sandbox replay", "run-sandbox.py", "Replays the native harness using Docker and Python; no product source access required."),
        ("checksums", "File checksums", "SHA256SUMS", "SHA-256 integrity checks for this package; not an attestation."),
    ]
    if (out / "baseline" / "run-result.json").exists():
        names.append(("baseline", "Unmodified compiler baseline", "baseline/run-result.json", "Real pre-fix run: compare actual outputs, not engine success alone."))
    manifest = {"example_id": EXAMPLE, "title": "Invoice rule: generated, then tested", "dataset_label": fixture["dataset_label"],
        "business": fixture["business"], "rule_id": fixture["rule_id"],
        "provenance": {"product_commit": commit, "compiler": "backend/services/workflow_ir/providers/n8n/compiler.py:N8nWorkflowCompiler",
                       "compiler_version": compiler.adapter_version, "n8n_version": version, "generated_at": datetime.now(timezone.utc).isoformat(),
                       "product_base_commit": args.product_base_commit, "product_pr_url": args.product_pr_url,
                       "product_status": "proposed_fix_pending_merge" if proposed_fix else "repository_baseline",
                       "compiler_worktree_modified": bool(source_status), "compiler_patch_sha256": patch_sha},
        "stages": [
            {"id": "capture", "label": "Source", "status": "synthetic", "summary": "Fictional Northstar invoices and owner/amount policy PS-RULE-01."},
            {"id": "understand", "label": "Understand", "status": "authored", "summary": "Missing owners return for clarification; owned invoices need a review queue."},
            {"id": "decide", "label": "Decision", "status": "authored", "summary": "Compare a checklist with a queue rule. This demonstration selects rule evaluation; nobody approves or pays an invoice."},
            {"id": "build", "label": "Generated artifact", "status": "generated", "summary": "Real product compiler output from manually authored IR; native JSON is unchanged."},
            {"id": "validate", "label": "Validation", "status": business_status, "summary": summary},
            {"id": "release", "label": "Sandbox execution", "status": business_status, "summary": "Actual local n8n run with a disclosed CLI harness. " + str(passed_count) + "/5 queue assertions passed; no production release."},
        ],
        "files": [{"id": i, "label": label, "path": path, "description": description} for i, label, path, description in names],
        "validation": validation, "execution": execution_result,
    }
    dump(out / "manifest.json", manifest)
    (out / "SHA256SUMS").write_text("".join(sha(path) + "  " + str(path.relative_to(out)) + "\n" for path in sorted(out.rglob("*")) if path.is_file() and path != out / "SHA256SUMS"))
    print(json.dumps({"package": str(out), "business_rule_status": business_status, "engine_status": engine_status,
                      "passed": passed_count, "total": len(cases), "fidelity_disposition": reports["primary"].disposition.value}))


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--product-repo", type=Path, required=True)
    parser.add_argument("--output", type=Path, default=ROOT / "public")
    parser.add_argument("--product-pr-url", default=None)
    parser.add_argument("--product-base-commit", default=None)
    parser.add_argument("--patch-output", type=Path, default=None, help="Optional private source diff path, outside the public output directory")
    asyncio.run(generate(parser.parse_args()))
