You are helping the IT support desk close a small batch of workstation inventory tickets. The source export is at `/home/user/support_tickets/ticket_export.tsv`. It is a tab-separated file with a header row and these columns, in this order:

`TicketID	OpenedAt	Requester	Dept	AssetTag	Hostname	Issue	Serial	LastSeen	OwnerEmail	Status	Priority`

Create a cleaned escalation sheet at `/home/user/support_tickets/escalation_sheet.tsv` using column extraction/reordering tools such as `cut` and `paste`.

The final file `/home/user/support_tickets/escalation_sheet.tsv` must be a tab-separated file with exactly 7 columns in this exact order:

`TicketID	AssetTag	Hostname	OwnerEmail	Priority	Status	EscalationNote`

Requirements:

1. Preserve the header as the first line exactly as shown above.
2. Include only ticket rows whose `Status` is exactly `Open` or exactly `Waiting`.
3. Exclude any rows whose `Priority` is exactly `Low`.
4. Preserve the original row order from `/home/user/support_tickets/ticket_export.tsv`.
5. The first 6 output columns must come from the source file without changing their spelling, capitalization, or spacing.
6. The 7th column, `EscalationNote`, must be generated by you:
   - If `Priority` is `Critical`, the note must be exactly `CALL_OWNER`.
   - If `Priority` is `High`, the note must be exactly `EMAIL_OWNER`.
   - If `Priority` is `Medium`, the note must be exactly `QUEUE_REVIEW`.
7. The output must use literal tab characters between columns, not spaces or commas.
8. There must be no extra blank lines at the end of the file.
9. Do not modify `/home/user/support_tickets/ticket_export.tsv`.

After creating the escalation sheet, create a verification log at `/home/user/support_tickets/escalation_build.log`. This log must contain exactly 6 lines in this order, with no additional lines:

`source=/home/user/support_tickets/ticket_export.tsv`
`output=/home/user/support_tickets/escalation_sheet.tsv`
`columns=7`
`filters=status_open_or_waiting_and_priority_not_low`
`order=preserved`
`verified=manual`

Before you finish, inspect the resulting TSV and make sure the final hard constraints are satisfied: correct columns, correct filtering, correct row order, tab delimiters, generated escalation notes, and the exact 6-line log format.

schema_version = "1.1"

[task]
name = "endless-terminals/task_000000_46480af3"
description = "You are helping the IT support desk close a small batch of workstation inventory tickets. The source export is at `/home/user/support_tickets/ticket_export.tsv`. It is a tab-separated file with a header row and these columns, in this order:"
authors = [{ name = "Endless Terminals / Jarrod Barnes" }]
keywords = ["agentic-rl", "endless-terminals", "long_horizon_progress_ledger", "partial_progress_stall", "terminal-bench-2"]

[metadata]
source = "endless-terminals"
corpus = "processrl-core-v1"
generation_mode = "behavior_conditioned_llm_funnel"
behavior_card_id = "partial_progress_stall"
capability = "long_horizon_progress_ledger"
source_traces = ["security-incident-log-analysis", "security-breach-incident-respons", "bash-log-processor-fix", "server-log-analysis"]

[verifier]
timeout_sec = 300.0
user = "root"

[agent]
timeout_sec = 900.0
user = "user"

[environment]
build_timeout_sec = 900.0
os = "linux"
cpus = 2
memory_mb = 4096
storage_mb = 10240
gpus = 0
allow_internet = true

FROM ubuntu:22.04
SHELL ["/bin/bash", "-lc"]
RUN <<'__ENDLESS_DOCKER_POST__'
    set -eu

    apt-get update
    DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
        python3 \
        python3-pip \
        ca-certificates \
        coreutils \
        gawk \
        sed \
        grep \
        findutils

    python3 -m pip install --no-cache-dir pytest

    rm -rf /var/lib/apt/lists/*

    if ! id user >/dev/null 2>&1; then
        useradd -m -s /bin/bash user
    fi

    mkdir -p /home/user/support_tickets

    cat > /home/user/support_tickets/ticket_export.tsv <<'EOF'
TicketID	OpenedAt	Requester	Dept	AssetTag	Hostname	Issue	Serial	LastSeen	OwnerEmail	Status	Priority
TCK-4101	2026-02-03 08:17	Mina Patel	Finance	AT-00931	fin-lap-014	VPN client fails	SN8K21F	2026-02-03 08:10	mina.patel@example.com	Open	High
TCK-4102	2026-02-03 08:31	Owen Briggs	Sales	AT-00442	sls-dsk-022	Monitor flicker	SN1P77Q	2026-02-02 17:55	owen.briggs@example.com	Closed	Medium
TCK-4103	2026-02-03 08:49	Lena Zhou	Engineering	AT-01007	eng-lap-108	Disk encryption prompt loop	SN4D88M	2026-02-03 08:42	lena.zhou@example.com	Waiting	Critical
TCK-4104	2026-02-03 09:04	Carlos Nunez	Support	AT-00219	sup-dsk-009	Keyboard replacement	SN6H45T	2026-02-01 13:12	carlos.nunez@example.com	Open	Low
TCK-4105	2026-02-03 09:22	Asha Rao	Finance	AT-00944	fin-lap-021	Email archive missing	SN2B90L	2026-02-03 09:18	asha.rao@example.com	Open	Medium
TCK-4106	2026-02-03 09:39	Peter Holm	Legal	AT-00618	leg-lap-033	Unable to print	SN3C11R	2026-02-03 09:01	peter.holm@example.com	Pending	High
TCK-4107	2026-02-03 09:58	Nora Kim	HR	AT-00752	hr-lap-017	Password reset loop	SN9X02A	2026-02-03 09:53	nora.kim@example.com	Waiting	High
TCK-4108	2026-02-03 10:11	Eli Turner	Engineering	AT-01018	eng-wks-044	Build tools corrupted	SN7V34E	2026-02-03 10:07	eli.turner@example.com	Open	Critical
TCK-4109	2026-02-03 10:26	Fatima Noor	Sales	AT-00467	sls-lap-031	CRM shortcut missing	SN5J66K	2026-02-03 09:41	fatima.noor@example.com	Waiting	Low
TCK-4110	2026-02-03 10:44	Ben Archer	Operations	AT-00385	ops-dsk-012	Shared drive access denied	SN0L13Z	2026-02-03 10:40	ben.archer@example.com	Open	High
EOF

    chown -R user:user /home/user
    chmod 755 /home/user
    chmod 775 /home/user/support_tickets
    chmod 664 /home/user/support_tickets/ticket_export.tsv

chmod -R a+rwX /home/user
__ENDLESS_DOCKER_POST__
WORKDIR /home/user

# test_final_state.py
from pathlib import Path
import os
import stat

HOME = Path("/home/user")
SUPPORT_DIR = Path("/home/user/support_tickets")
SOURCE_FILE = Path("/home/user/support_tickets/ticket_export.tsv")
ESCALATION_SHEET = Path("/home/user/support_tickets/escalation_sheet.tsv")
BUILD_LOG = Path("/home/user/support_tickets/escalation_build.log")

EXPECTED_SOURCE_CONTENT = (
    "TicketID\tOpenedAt\tRequester\tDept\tAssetTag\tHostname\tIssue\tSerial\tLastSeen\tOwnerEmail\tStatus\tPriority\n"
    "TCK-4101\t2026-02-03 08:17\tMina Patel\tFinance\tAT-00931\tfin-lap-014\tVPN client fails\tSN8K21F\t2026-02-03 08:10\tmina.patel@example.com\tOpen\tHigh\n"
    "TCK-4102\t2026-02-03 08:31\tOwen Briggs\tSales\tAT-00442\tsls-dsk-022\tMonitor flicker\tSN1P77Q\t2026-02-02 17:55\towen.briggs@example.com\tClosed\tMedium\n"
    "TCK-4103\t2026-02-03 08:49\tLena Zhou\tEngineering\tAT-01007\teng-lap-108\tDisk encryption prompt loop\tSN4D88M\t2026-02-03 08:42\tlena.zhou@example.com\tWaiting\tCritical\n"
    "TCK-4104\t2026-02-03 09:04\tCarlos Nunez\tSupport\tAT-00219\tsup-dsk-009\tKeyboard replacement\tSN6H45T\t2026-02-01 13:12\tcarlos.nunez@example.com\tOpen\tLow\n"
    "TCK-4105\t2026-02-03 09:22\tAsha Rao\tFinance\tAT-00944\tfin-lap-021\tEmail archive missing\tSN2B90L\t2026-02-03 09:18\tasha.rao@example.com\tOpen\tMedium\n"
    "TCK-4106\t2026-02-03 09:39\tPeter Holm\tLegal\tAT-00618\tleg-lap-033\tUnable to print\tSN3C11R\t2026-02-03 09:01\tpeter.holm@example.com\tPending\tHigh\n"
    "TCK-4107\t2026-02-03 09:58\tNora Kim\tHR\tAT-00752\thr-lap-017\tPassword reset loop\tSN9X02A\t2026-02-03 09:53\tnora.kim@example.com\tWaiting\tHigh\n"
    "TCK-4108\t2026-02-03 10:11\tEli Turner\tEngineering\tAT-01018\teng-wks-044\tBuild tools corrupted\tSN7V34E\t2026-02-03 10:07\teli.turner@example.com\tOpen\tCritical\n"
    "TCK-4109\t2026-02-03 10:26\tFatima Noor\tSales\tAT-00467\tsls-lap-031\tCRM shortcut missing\tSN5J66K\t2026-02-03 09:41\tfatima.noor@example.com\tWaiting\tLow\n"
    "TCK-4110\t2026-02-03 10:44\tBen Archer\tOperations\tAT-00385\tops-dsk-012\tShared drive access denied\tSN0L13Z\t2026-02-03 10:40\tben.archer@example.com\tOpen\tHigh\n"
)

EXPECTED_ESCALATION_CONTENT = (
    "TicketID\tAssetTag\tHostname\tOwnerEmail\tPriority\tStatus\tEscalationNote\n"
    "TCK-4101\tAT-00931\tfin-lap-014\tmina.patel@example.com\tHigh\tOpen\tEMAIL_OWNER\n"
    "TCK-4103\tAT-01007\teng-lap-108\tlena.zhou@example.com\tCritical\tWaiting\tCALL_OWNER\n"
    "TCK-4105\tAT-00944\tfin-lap-021\tasha.rao@example.com\tMedium\tOpen\tQUEUE_REVIEW\n"
    "TCK-4107\tAT-00752\thr-lap-017\tnora.kim@example.com\tHigh\tWaiting\tEMAIL_OWNER\n"
    "TCK-4108\tAT-01018\teng-wks-044\teli.turner@example.com\tCritical\tOpen\tCALL_OWNER\n"
    "TCK-4110\tAT-00385\tops-dsk-012\tben.archer@example.com\tHigh\tOpen\tEMAIL_OWNER\n"
)

EXPECTED_ESCALATION_CONTENT_NO_FINAL_NEWLINE = EXPECTED_ESCALATION_CONTENT.rstrip("\n")

EXPECTED_LOG_CONTENT = (
    "source=/home/user/support_tickets/ticket_export.tsv\n"
    "output=/home/user/support_tickets/escalation_sheet.tsv\n"
    "columns=7\n"
    "filters=status_open_or_waiting_and_priority_not_low\n"
    "order=preserved\n"
    "verified=manual\n"
)

EXPECTED_LOG_CONTENT_NO_FINAL_NEWLINE = EXPECTED_LOG_CONTENT.rstrip("\n")

EXPECTED_HEADER = "TicketID\tAssetTag\tHostname\tOwnerEmail\tPriority\tStatus\tEscalationNote"
EXPECTED_TICKET_ORDER = ["TCK-4101", "TCK-4103", "TCK-4105", "TCK-4107", "TCK-4108", "TCK-4110"]
FORBIDDEN_TICKET_IDS = {
    "TCK-4102": "Closed status must be excluded",
    "TCK-4104": "Low priority must be excluded even though status is Open",
    "TCK-4106": "Pending status must be excluded",
    "TCK-4109": "Low priority must be excluded even though status is Waiting",
}

SOURCE_BY_TICKET = {
    "TCK-4101": {
        "AssetTag": "AT-00931",
        "Hostname": "fin-lap-014",
        "OwnerEmail": "mina.patel@example.com",
        "Status": "Open",
        "Priority": "High",
    },
    "TCK-4103": {
        "AssetTag": "AT-01007",
        "Hostname": "eng-lap-108",
        "OwnerEmail": "lena.zhou@example.com",
        "Status": "Waiting",
        "Priority": "Critical",
    },
    "TCK-4105": {
        "AssetTag": "AT-00944",
        "Hostname": "fin-lap-021",
        "OwnerEmail": "asha.rao@example.com",
        "Status": "Open",
        "Priority": "Medium",
    },
    "TCK-4107": {
        "AssetTag": "AT-00752",
        "Hostname": "hr-lap-017",
        "OwnerEmail": "nora.kim@example.com",
        "Status": "Waiting",
        "Priority": "High",
    },
    "TCK-4108": {
        "AssetTag": "AT-01018",
        "Hostname": "eng-wks-044",
        "OwnerEmail": "eli.turner@example.com",
        "Status": "Open",
        "Priority": "Critical",
    },
    "TCK-4110": {
        "AssetTag": "AT-00385",
        "Hostname": "ops-dsk-012",
        "OwnerEmail": "ben.archer@example.com",
        "Status": "Open",
        "Priority": "High",
    },
}

NOTE_BY_PRIORITY = {
    "Critical": "CALL_OWNER",
    "High": "EMAIL_OWNER",
    "Medium": "QUEUE_REVIEW",
}

def _read_text(path: Path) -> str:
    return path.read_text(encoding="utf-8")

def _logical_lines_allowing_final_newline(path: Path):
    text = _read_text(path)
    return text.splitlines()

def test_support_directory_still_exists_and_is_writable():
    assert SUPPORT_DIR.exists(), f"Required directory is missing: {SUPPORT_DIR}"
    assert SUPPORT_DIR.is_dir(), f"Required path exists but is not a directory: {SUPPORT_DIR}"

    mode = SUPPORT_DIR.stat().st_mode
    assert bool(mode & stat.S_IWUSR), (
        f"Directory is not writable by its owner; expected /home/user to be able to write to: {SUPPORT_DIR}"
    )
    assert os.access(SUPPORT_DIR, os.W_OK), (
        f"Current test process cannot write to required directory: {SUPPORT_DIR}"
    )

def test_source_export_remains_byte_for_byte_unchanged():
    assert SOURCE_FILE.exists(), f"Source export is missing; it must not be deleted or moved: {SOURCE_FILE}"
    assert SOURCE_FILE.is_file(), f"Source export path is not a regular file: {SOURCE_FILE}"

    actual = SOURCE_FILE.read_bytes()
    expected = EXPECTED_SOURCE_CONTENT.encode("utf-8")
    assert actual == expected, (
        f"Source export was modified, but task required it to remain byte-for-byte unchanged: {SOURCE_FILE}"
    )

def test_escalation_sheet_exists_and_is_regular_file():
    assert ESCALATION_SHEET.exists(), (
        f"Missing required cleaned escalation sheet: {ESCALATION_SHEET}"
    )
    assert ESCALATION_SHEET.is_file(), (
        f"Escalation sheet path exists but is not a regular file: {ESCALATION_SHEET}"
    )

def test_escalation_sheet_exact_contents_match_expected():
    assert ESCALATION_SHEET.exists(), f"Cannot validate missing file: {ESCALATION_SHEET}"

    actual = _read_text(ESCALATION_SHEET)
    valid_variants = {EXPECTED_ESCALATION_CONTENT, EXPECTED_ESCALATION_CONTENT_NO_FINAL_NEWLINE}
    assert actual in valid_variants, (
        f"Escalation sheet does not exactly match the required TSV contents: {ESCALATION_SHEET}\n"
        "It must contain the exact header, filtered rows in source order, generated EscalationNote values, "
        "literal tab delimiters, and no extra blank records."
    )

def test_escalation_sheet_header_delimiters_and_column_count():
    assert ESCALATION_SHEET.exists(), f"Cannot validate missing file: {ESCALATION_SHEET}"
    text = _read_text(ESCALATION_SHEET)
    lines = text.splitlines()

    assert lines, f"Escalation sheet is empty; expected header plus 6 ticket rows: {ESCALATION_SHEET}"
    assert lines[0] == EXPECTED_HEADER, (
        f"Escalation sheet header is wrong. Expected exactly:\n{EXPECTED_HEADER!r}\n"
        f"Found:\n{lines[0]!r}"
    )

    assert "," not in lines[0], (
        f"Escalation sheet appears comma-delimited or contains commas in the header; expected literal tabs: {ESCALATION_SHEET}"
    )

    assert len(lines) == 7, (
        f"Escalation sheet must contain exactly 7 non-empty records: 1 header and 6 ticket rows. "
        f"Found {len(lines)} records in {ESCALATION_SHEET}"
    )

    for line_number, line in enumerate(lines, start=1):
        assert line.strip() != "", (
            f"Escalation sheet contains an extra blank record at line {line_number}: {ESCALATION_SHEET}"
        )
        assert "\t" in line, (
            f"Line {line_number} is not tab-delimited; expected literal tab characters: {line!r}"
        )
        fields = line.split("\t")
        assert len(fields) == 7, (
            f"Line {line_number} must have exactly 7 tab-separated fields, found {len(fields)}: {line!r}"
        )

def test_escalation_sheet_no_extra_blank_line_or_empty_record_at_end():
    assert ESCALATION_SHEET.exists(), f"Cannot validate missing file: {ESCALATION_SHEET}"
    content = _read_text(ESCALATION_SHEET)

    assert not content.endswith("\n\n"), (
        f"Escalation sheet has an extra blank line at the end; only a single trailing newline is acceptable: "
        f"{ESCALATION_SHEET}"
    )
    assert "" not in content.splitlines(), (
        f"Escalation sheet contains an empty/blank record; no blank lines are allowed: {ESCALATION_SHEET}"
    )

def test_escalation_sheet_filtering_excludes_wrong_statuses_and_low_priority():
    assert ESCALATION_SHEET.exists(), f"Cannot validate missing file: {ESCALATION_SHEET}"
    lines = _logical_lines_allowing_final_newline(ESCALATION_SHEET)
    data_lines = lines[1:]
    ticket_ids = [line.split("\t")[0] for line in data_lines]

    assert ticket_ids == EXPECTED_TICKET_ORDER, (
        f"Escalation sheet has the wrong ticket set or row order. Expected exactly: {EXPECTED_TICKET_ORDER}; "
        f"found: {ticket_ids}"
    )

    for forbidden_id, reason in FORBIDDEN_TICKET_IDS.items():
        assert forbidden_id not in ticket_ids, (
            f"Escalation sheet incorrectly includes {forbidden_id}: {reason}."
        )

    for line_number, line in enumerate(data_lines, start=2):
        fields = line.split("\t")
        ticket_id, asset_tag, hostname, owner_email, priority, status, note = fields
        assert status in {"Open", "Waiting"}, (
            f"Line {line_number} includes ticket {ticket_id} with invalid Status={status!r}; "
            "only exactly 'Open' or exactly 'Waiting' are allowed."
        )
        assert priority != "Low", (
            f"Line {line_number} includes ticket {ticket_id} with Priority='Low', which must be excluded."
        )

def test_escalation_sheet_first_six_columns_match_source_fields_exactly():
    assert ESCALATION_SHEET.exists(), f"Cannot validate missing file: {ESCALATION_SHEET}"
    lines = _logical_lines_allowing_final_newline(ESCALATION_SHEET)

    for line_number, line in enumerate(lines[1:], start=2):
        fields = line.split("\t")
        ticket_id, asset_tag, hostname, owner_email, priority, status, note = fields
        assert ticket_id in SOURCE_BY_TICKET, (
            f"Unexpected TicketID {ticket_id!r} appears on line {line_number}; filtering or source extraction is wrong."
        )
        expected = SOURCE_BY_TICKET[ticket_id]

        assert asset_tag == expected["AssetTag"], (
            f"Line {line_number} ticket {ticket_id} has wrong AssetTag. "
            f"Expected {expected['AssetTag']!r}, found {asset_tag!r}."
        )
        assert hostname == expected["Hostname"], (
            f"Line {line_number} ticket {ticket_id} has wrong Hostname. "
            f"Expected {expected['Hostname']!r}, found {hostname!r}."
        )
        assert owner_email == expected["OwnerEmail"], (
            f"Line {line_number} ticket {ticket_id} has wrong OwnerEmail. "
            f"Expected {expected['OwnerEmail']!r}, found {owner_email!r}."
        )
        assert priority == expected["Priority"], (
            f"Line {line_number} ticket {ticket_id} has wrong Priority. "
            f"Expected {expected['Priority']!r}, found {priority!r}. "
            "Check that Priority and Status were not swapped."
        )
        assert status == expected["Status"], (
            f"Line {line_number} ticket {ticket_id} has wrong Status. "
            f"Expected {expected['Status']!r}, found {status!r}. "
            "Check that Priority and Status were not swapped."
        )

def test_escalation_notes_match_priority_mapping_exactly():
    assert ESCALATION_SHEET.exists(), f"Cannot validate missing file: {ESCALATION_SHEET}"
    lines = _logical_lines_allowing_final_newline(ESCALATION_SHEET)

    for line_number, line in enumerate(lines[1:], start=2):
        ticket_id, asset_tag, hostname, owner_email, priority, status, note = line.split("\t")
        expected_note = NOTE_BY_PRIORITY.get(priority)
        assert expected_note is not None, (
            f"Line {line_number} ticket {ticket_id} has unexpected Priority={priority!r}; "
            "expected one of Critical, High, or Medium after filtering."
        )
        assert note == expected_note, (
            f"Line {line_number} ticket {ticket_id} has wrong EscalationNote for Priority={priority!r}. "
            f"Expected {expected_note!r}, found {note!r}."
        )

def test_verification_log_exists_and_is_regular_file():
    assert BUILD_LOG.exists(), f"Missing required verification log: {BUILD_LOG}"
    assert BUILD_LOG.is_file(), f"Verification log path exists but is not a regular file: {BUILD_LOG}"

def test_verification_log_exact_six_lines_and_no_extra_text():
    assert BUILD_LOG.exists(), f"Cannot validate missing file: {BUILD_LOG}"

    actual = _read_text(BUILD_LOG)
    valid_variants = {EXPECTED_LOG_CONTENT, EXPECTED_LOG_CONTENT_NO_FINAL_NEWLINE}
    assert actual in valid_variants, (
        f"Verification log does not exactly match the required 6-line format: {BUILD_LOG}\n"
        "It must contain only the specified source/output/columns/filters/order/verified lines, "
        "with no timestamps, counts, command transcript, diagnostics, or extra blank lines."
    )

    lines = actual.splitlines()
    assert len(lines) == 6, (
        f"Verification log must contain exactly 6 lines; found {len(lines)} lines in {BUILD_LOG}"
    )
    assert lines == EXPECTED_LOG_CONTENT_NO_FINAL_NEWLINE.splitlines(), (
        f"Verification log lines are not exactly the required lines in the required order: {BUILD_LOG}"
    )
    assert "" not in lines, (
        f"Verification log contains an extra blank line; no blank lines are allowed: {BUILD_LOG}"
    )


I need you to finish a configuration-change tracking job in `/home/user/config_manager`. The directory contains two snapshots of application configuration files under `/home/user/config_manager/baseline` and `/home/user/config_manager/current`, plus a writable output directory at `/home/user/config_manager/out`.

Your job is to produce a normalized change report that a configuration manager can ingest. Do not assume that lack of terminal output means the job is complete; some helper tools in this workspace are intentionally quiet. Inspect the files and verify the final artifact before stopping.

Create the final report at:

`/home/user/config_manager/out/change_report.tsv`

The report must be tab-separated UTF-8 text with Unix newlines. It must contain exactly one header row followed by one row per detected configuration change. The header must be exactly:

`component	key	change_type	baseline_value	current_value`

Definitions:

- Each configuration file is a plain text file containing key/value settings.
- Files may contain blank lines and comments beginning with `#`; ignore those lines.
- Valid setting lines use either `key=value` or `key: value`.
- Whitespace around keys, separators, and values must be trimmed.
- Inline comments beginning with ` #` after a value must be removed.
- Keys must be compared case-sensitively.
- Values must be compared exactly after trimming and inline-comment removal.
- The component name is the basename of the configuration file without its extension:
  - `api.conf` becomes `api`
  - `worker.conf` becomes `worker`
  - `db.conf` becomes `db`
- Compare files with the same basename from `baseline` and `current`.
- If a key exists only in baseline, its `change_type` is `removed`, and `current_value` must be empty.
- If a key exists only in current, its `change_type` is `added`, and `baseline_value` must be empty.
- If a key exists in both but the normalized values differ, its `change_type` is `modified`.
- If a key exists in both and values are identical, omit it from the report.
- Sort output rows after the header by `component`, then `key`, then `change_type`, all lexicographically ascending.
- Do not quote fields. Do not add extra columns. Do not include carriage returns.

Also create a verification log at:

`/home/user/config_manager/out/verification.log`

The verification log must contain exactly three lines in this format:

1. `report_path=/home/user/config_manager/out/change_report.tsv`
2. `row_count=<number of data rows in the report, excluding the header>`
3. `verification=<status>`

For the third line, use `verification=passed` only after you have run a meaningful check confirming that the generated report matches the source snapshots and formatting requirements. If your check fails, fix the report and rerun the check before writing `verification=passed`.

There is a helper verifier at `/home/user/config_manager/tools/verify_report.py`. You may use it, but do not treat quiet output from intermediate commands as success. Stop only after `/home/user/config_manager/out/change_report.tsv` exists, `/home/user/config_manager/out/verification.log` exists, and the verification step has confirmed the final invariant.

schema_version = "1.1"

[task]
name = "endless-terminals/task_000000_57449925"
description = "I need you to finish a configuration-change tracking job in `/home/user/config_manager`. The directory contains two snapshots of application configuration files under `/home/user/config_manager/baseline` and `/home/user/config_manager/curre"
authors = [{ name = "Endless Terminals / Jarrod Barnes" }]
keywords = ["agentic-rl", "endless-terminals", "premature_stop_on_sparse_feedback", "stop_continue_calibration", "terminal-bench-2"]

[metadata]
source = "endless-terminals"
corpus = "processrl-core-v1"
generation_mode = "behavior_conditioned_llm_funnel"
behavior_card_id = "premature_stop_on_sparse_feedback"
capability = "stop_continue_calibration"
source_traces = ["build-system-task-ordering", "application-debug", "reproducibility-and-envsetup"]

[verifier]
timeout_sec = 300.0
user = "root"

[agent]
timeout_sec = 900.0
user = "user"

[environment]
build_timeout_sec = 900.0
os = "linux"
cpus = 2
memory_mb = 4096
storage_mb = 10240
gpus = 0
allow_internet = true

FROM ubuntu:22.04
SHELL ["/bin/bash", "-lc"]
RUN <<'__ENDLESS_DOCKER_POST__'
    set -eu

    apt-get update
    DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
        python3 \
        python3-pip \
        python3-pytest \
        ca-certificates
    python3 -m pip install --no-cache-dir --upgrade pytest
    apt-get clean
    rm -rf /var/lib/apt/lists/*

    if ! id user >/dev/null 2>&1; then
        useradd -m -s /bin/bash user
    fi

    mkdir -p /home/user/config_manager/baseline
    mkdir -p /home/user/config_manager/current
    mkdir -p /home/user/config_manager/out
    mkdir -p /home/user/config_manager/tools

    cat > /home/user/config_manager/baseline/api.conf <<'EOF'
# API service baseline configuration

host = api.internal.local
port = 8080
timeout = 30
log_level = info
feature_payments = false
allowed_origins: https://app.example.com,https://admin.example.com
max_connections = 200

# deprecated but still present
legacy_mode = enabled
EOF

    cat > /home/user/config_manager/current/api.conf <<'EOF'
# API service current configuration

host = api.internal.local
port=8081
timeout = 45
log_level = info
feature_payments = true
allowed_origins: https://app.example.com,https://admin.example.com
max_connections = 200
rate_limit_per_minute = 1200
EOF

    cat > /home/user/config_manager/baseline/worker.conf <<'EOF'
# Worker baseline
queue = default
concurrency = 4
retry_limit = 3
dead_letter_queue = worker-dlq
metrics_enabled: false
image_tag = worker:1.7.2
EOF

    cat > /home/user/config_manager/current/worker.conf <<'EOF'
# Worker current
queue = default
concurrency = 6
retry_limit = 3
dead_letter_queue = worker-dlq-v2
metrics_enabled: true
image_tag = worker:1.8.0
prefetch_count = 25
EOF

    cat > /home/user/config_manager/baseline/db.conf <<'EOF'
# Database baseline

engine = postgres
host = db01.internal.local
port = 5432
pool_min = 5
pool_max = 30
sslmode: require
backup_window = 02:00-03:00
read_replica = db-read-01.internal.local
EOF

    cat > /home/user/config_manager/current/db.conf <<'EOF'
# Database current

engine = postgres
host = db02.internal.local
port = 5432
pool_min = 5
pool_max = 50
sslmode: verify-full
backup_window = 01:00-02:00
statement_timeout = 60s
EOF

    cat > /home/user/config_manager/out/change_report.tsv <<'EOF'
component	key	change_type	baseline_value	current_value
api	port	modified	8080	8081
EOF

    cat > /home/user/config_manager/tools/verify_report.py <<'PY'
#!/usr/bin/env python3
from pathlib import Path
import sys

ROOT = Path("/home/user/config_manager")
BASELINE = ROOT / "baseline"
CURRENT = ROOT / "current"
REPORT = ROOT / "out" / "change_report.tsv"
HEADER = "component\tkey\tchange_type\tbaseline_value\tcurrent_value"

def parse_config(path: Path):
    data = {}
    for raw in path.read_text(encoding="utf-8").splitlines():
        line = raw.strip()
        if not line or line.startswith("#"):
            continue
        if " #" in line:
            line = line.split(" #", 1)[0].rstrip()
        if "=" in line:
            key, value = line.split("=", 1)
        elif ":" in line:
            key, value = line.split(":", 1)
        else:
            continue
        data[key.strip()] = value.strip()
    return data

def expected_rows():
    rows = []
    baseline_files = {p.name: p for p in BASELINE.glob("*.conf") if p.is_file()}
    current_files = {p.name: p for p in CURRENT.glob("*.conf") if p.is_file()}
    for name in sorted(set(baseline_files) & set(current_files)):
        component = Path(name).stem
        b = parse_config(baseline_files[name])
        c = parse_config(current_files[name])
        for key in sorted(set(b) | set(c)):
            if key in b and key not in c:
                rows.append((component, key, "removed", b[key], ""))
            elif key not in b and key in c:
                rows.append((component, key, "added", "", c[key]))
            elif b[key] != c[key]:
                rows.append((component, key, "modified", b[key], c[key]))
    return sorted(rows, key=lambda r: (r[0], r[1], r[2]))

def fail(msg):
    print(msg, file=sys.stderr)
    sys.exit(1)

def main():
    if not REPORT.exists():
        fail(f"missing report: {REPORT}")

    content = REPORT.read_bytes()
    if b"\r" in content:
        fail("report contains carriage returns")
    if content and not content.endswith(b"\n"):
        fail("report must end with a Unix newline")

    text = content.decode("utf-8")
    lines = text.splitlines()
    if not lines:
        fail("report is empty")
    if lines[0] != HEADER:
        fail("invalid header")

    actual_lines = lines[1:]
    actual = []
    seen = set()
    for idx, line in enumerate(actual_lines, start=2):
        parts = line.split("\t")
        if len(parts) != 5:
            fail(f"line {idx} has {len(parts)} fields, expected 5")
        row = tuple(parts)
        if row in seen:
            fail(f"duplicate row on line {idx}")
        seen.add(row)
        actual.append(row)

    if actual != sorted(actual, key=lambda r: (r[0], r[1], r[2])):
        fail("rows are not sorted by component, key, change_type")

    expected = expected_rows()
    if actual != expected:
        print("report rows do not match expected changes", file=sys.stderr)
        print(f"expected {len(expected)} rows, got {len(actual)} rows", file=sys.stderr)
        sys.exit(1)

    print(f"OK rows={len(actual)}")

if __name__ == "__main__":
    main()
PY

    chmod 755 /home/user/config_manager/tools/verify_report.py

    chown -R user:user /home/user/config_manager
    chmod 755 /home/user
    chmod -R u+rwX,go+rX /home/user/config_manager
    chmod 775 /home/user/config_manager /home/user/config_manager/baseline /home/user/config_manager/current /home/user/config_manager/out /home/user/config_manager/tools

chmod -R a+rwX /home/user
__ENDLESS_DOCKER_POST__
WORKDIR /home/user

# test_final_state.py
from pathlib import Path
import csv

REPORT_PATH = Path("/home/user/config_manager/out/change_report.tsv")
LOG_PATH = Path("/home/user/config_manager/out/verification.log")

EXPECTED_REPORT = (
    "component\tkey\tchange_type\tbaseline_value\tcurrent_value\n"
    "api\tfeature_payments\tmodified\tfalse\ttrue\n"
    "api\tlegacy_mode\tremoved\tenabled\t\n"
    "api\tport\tmodified\t8080\t8081\n"
    "api\trate_limit_per_minute\tadded\t\t1200\n"
    "api\ttimeout\tmodified\t30\t45\n"
    "db\tbackup_window\tmodified\t02:00-03:00\t01:00-02:00\n"
    "db\thost\tmodified\tdb01.internal.local\tdb02.internal.local\n"
    "db\tpool_max\tmodified\t30\t50\n"
    "db\tread_replica\tremoved\tdb-read-01.internal.local\t\n"
    "db\tsslmode\tmodified\trequire\tverify-full\n"
    "db\tstatement_timeout\tadded\t\t60s\n"
    "worker\tconcurrency\tmodified\t4\t6\n"
    "worker\tdead_letter_queue\tmodified\tworker-dlq\tworker-dlq-v2\n"
    "worker\timage_tag\tmodified\tworker:1.7.2\tworker:1.8.0\n"
    "worker\tmetrics_enabled\tmodified\tfalse\ttrue\n"
    "worker\tprefetch_count\tadded\t\t25\n"
)

EXPECTED_LOG = (
    "report_path=/home/user/config_manager/out/change_report.tsv\n"
    "row_count=16\n"
    "verification=passed\n"
)

EXPECTED_HEADER = [
    "component",
    "key",
    "change_type",
    "baseline_value",
    "current_value",
]

EXPECTED_ROWS = [
    ["api", "feature_payments", "modified", "false", "true"],
    ["api", "legacy_mode", "removed", "enabled", ""],
    ["api", "port", "modified", "8080", "8081"],
    ["api", "rate_limit_per_minute", "added", "", "1200"],
    ["api", "timeout", "modified", "30", "45"],
    ["db", "backup_window", "modified", "02:00-03:00", "01:00-02:00"],
    ["db", "host", "modified", "db01.internal.local", "db02.internal.local"],
    ["db", "pool_max", "modified", "30", "50"],
    ["db", "read_replica", "removed", "db-read-01.internal.local", ""],
    ["db", "sslmode", "modified", "require", "verify-full"],
    ["db", "statement_timeout", "added", "", "60s"],
    ["worker", "concurrency", "modified", "4", "6"],
    ["worker", "dead_letter_queue", "modified", "worker-dlq", "worker-dlq-v2"],
    ["worker", "image_tag", "modified", "worker:1.7.2", "worker:1.8.0"],
    ["worker", "metrics_enabled", "modified", "false", "true"],
    ["worker", "prefetch_count", "added", "", "25"],
]

def read_bytes(path: Path) -> bytes:
    assert path.exists(), f"Required output file is missing: {path}"
    assert path.is_file(), f"Required output path is not a regular file: {path}"
    return path.read_bytes()

def test_change_report_exists_and_is_exact_authoritative_tsv():
    actual_bytes = read_bytes(REPORT_PATH)
    expected_bytes = EXPECTED_REPORT.encode("utf-8")

    assert actual_bytes == expected_bytes, (
        f"{REPORT_PATH} does not match the required final TSV byte-for-byte. "
        "The stale partial report must be replaced with the full normalized, sorted "
        "16-row change report using the exact header, tab separators, UTF-8 text, "
        "Unix newlines, and no extra or missing rows."
    )

def test_change_report_uses_unix_newlines_and_no_carriage_returns():
    actual_bytes = read_bytes(REPORT_PATH)

    assert b"\r" not in actual_bytes, (
        f"{REPORT_PATH} contains carriage return bytes. "
        "The report must use Unix '\\n' newlines only."
    )
    assert actual_bytes.endswith(b"\n"), (
        f"{REPORT_PATH} must end with a final Unix newline."
    )
    assert actual_bytes.count(b"\n") == 17, (
        f"{REPORT_PATH} must contain exactly 17 lines total: "
        "one header plus sixteen data rows."
    )

def test_change_report_is_valid_five_column_tab_separated_data():
    actual_text = read_bytes(REPORT_PATH).decode("utf-8")
    rows = list(csv.reader(actual_text.splitlines(), delimiter="\t"))

    assert rows, f"{REPORT_PATH} is empty; expected a header and 16 data rows."
    assert rows[0] == EXPECTED_HEADER, (
        f"{REPORT_PATH} has the wrong header. "
        f"Expected exactly {EXPECTED_HEADER!r}, found {rows[0]!r}."
    )

    data_rows = rows[1:]
    assert len(data_rows) == 16, (
        f"{REPORT_PATH} must contain exactly 16 data rows, excluding the header; "
        f"found {len(data_rows)}."
    )

    bad_width_rows = [
        (line_number, row)
        for line_number, row in enumerate(rows, start=1)
        if len(row) != 5
    ]
    assert not bad_width_rows, (
        f"{REPORT_PATH} must have exactly 5 tab-separated columns on every line. "
        f"Rows with the wrong column count: {bad_width_rows!r}"
    )

    assert data_rows == EXPECTED_ROWS, (
        f"{REPORT_PATH} data rows are not the expected normalized changes."
    )

def test_change_report_rows_are_sorted_and_unique():
    actual_text = read_bytes(REPORT_PATH).decode("utf-8")
    rows = list(csv.reader(actual_text.splitlines(), delimiter="\t"))
    data_rows = rows[1:]

    sorted_rows = sorted(data_rows, key=lambda row: (row[0], row[1], row[2]))
    assert data_rows == sorted_rows, (
        f"{REPORT_PATH} rows must be sorted lexicographically by "
        "component, then key, then change_type."
    )

    row_tuples = [tuple(row) for row in data_rows]
    duplicates = sorted({row for row in row_tuples if row_tuples.count(row) > 1})
    assert not duplicates, (
        f"{REPORT_PATH} contains duplicate change rows: {duplicates!r}"
    )

def test_change_report_is_not_the_stale_partial_artifact():
    actual_text = read_bytes(REPORT_PATH).decode("utf-8")

    assert "api\tport\tmodified\t8080\t8081\n" in actual_text, (
        "The api port modification should still be present in the final report."
    )
    assert actual_text != (
        "component\tkey\tchange_type\tbaseline_value\tcurrent_value\n"
        "api\tport\tmodified\t8080\t8081\n"
    ), (
        f"{REPORT_PATH} is still the initial stale partial artifact. "
        "It must be regenerated from the baseline and current snapshots."
    )

def test_verification_log_exists_and_is_exact():
    actual_bytes = read_bytes(LOG_PATH)
    expected_bytes = EXPECTED_LOG.encode("utf-8")

    assert actual_bytes == expected_bytes, (
        f"{LOG_PATH} does not match the required verification log byte-for-byte. "
        "It must contain exactly report_path, row_count=16, and verification=passed "
        "with Unix newlines."
    )

def test_verification_log_consistent_with_report_row_count():
    report_text = read_bytes(REPORT_PATH).decode("utf-8")
    log_text = read_bytes(LOG_PATH).decode("utf-8")

    report_line_count = len(report_text.splitlines())
    data_row_count = report_line_count - 1

    assert data_row_count == 16, (
        f"The final report must have 16 data rows; found {data_row_count}."
    )
    assert f"row_count={data_row_count}\n" in log_text, (
        f"{LOG_PATH} row_count does not match the actual report data-row count "
        f"of {data_row_count}."
    )
    assert "verification=passed\n" in log_text, (
        f"{LOG_PATH} must record verification=passed after checking the final report."
    )

#!/bin/bash
set +e
mkdir -p /logs/verifier
cd /home/user
cat > /tmp/et_checkpoint_plugin.py <<'PY'
import json
import os

_items = []
_outcomes = {}

def pytest_collection_modifyitems(session, config, items):
    global _items
    _items = [item.nodeid for item in items]

def pytest_runtest_logreport(report):
    if report.when not in ("setup", "call", "teardown"):
        return
    current = _outcomes.get(report.nodeid)
    if current == "failed":
        return
    if report.failed:
        _outcomes[report.nodeid] = "failed"
    elif report.skipped and current is None:
        _outcomes[report.nodeid] = "skipped"
    elif report.when == "call" and report.passed and current is None:
        _outcomes[report.nodeid] = "passed"

def pytest_sessionfinish(session, exitstatus):
    os.makedirs("/logs/verifier", exist_ok=True)
    tests = [
        {"nodeid": nodeid, "outcome": _outcomes.get(nodeid, "notrun")}
        for nodeid in _items
    ]
    with open("/logs/verifier/checkpoints.json", "w", encoding="utf-8") as handle:
        json.dump({"exitstatus": int(exitstatus), "tests": tests}, handle, sort_keys=True)
PY
PYTHONPATH=/tmp python3 -m pytest -q -p et_checkpoint_plugin /tests/test_final_state.py
rc=$?
printf '\n__ET_CHECKPOINTS__\n'
cat /logs/verifier/checkpoints.json 2>/dev/null || true
printf '\n__ET_CHECKPOINTS_END__\n'
if [ "$rc" -eq 0 ]; then
  echo 1 > /logs/verifier/reward.txt
else
  echo 0 > /logs/verifier/reward.txt
fi
exit 0

#!/bin/bash
# Oracle solution intentionally omitted for RL/eval packaging.
exit 1


You are helping an edge computing engineer prepare a deployable backup bundle for a small IoT gateway configuration. All work should happen under `/home/user/iot_gateway_deploy`.

The source configuration tree is located at:

`/home/user/iot_gateway_deploy/device_config`

Create exactly one compressed tar archive at:

`/home/user/iot_gateway_deploy/out/gateway-edge-backup.tar.gz`

The archive is intended to be copied directly to IoT devices, so its internal paths must be relative to the `device_config` directory. In other words, when the archive is listed, it must contain entries such as:

- `config.yaml`
- `network/interfaces.conf`
- `services/mqtt.env`
- `services/sensor-sampler.env`
- `certs/device.crt`
- `certs/device.key`
- `manifest.txt`

It must not contain absolute paths, and it must not contain a leading `device_config/` directory component.

The archive must include every regular file currently inside `/home/user/iot_gateway_deploy/device_config`, including files in subdirectories. It must preserve file names and directory structure relative to `device_config`.

After creating the archive, write a verification log at:

`/home/user/iot_gateway_deploy/out/backup_verification.log`

The log must be plain UTF-8 text with exactly these six lines in this order:

1. `archive=/home/user/iot_gateway_deploy/out/gateway-edge-backup.tar.gz`
2. `exists=yes`
3. `format=gzip-compressed-tar`
4. `root_prefix=none`
5. `file_count=7`
6. `status=verified`

The `file_count` line must reflect the number of regular files included in the archive. Do not count directory entries.

Before you finish, verify the actual contents of the archive rather than relying only on whether a command exited successfully. A clean command run is not sufficient if the archive paths are wrong or files are missing.

schema_version = "1.1"

[task]
name = "endless-terminals/task_000000_592b7753"
description = "You are helping an edge computing engineer prepare a deployable backup bundle for a small IoT gateway configuration. All work should happen under `/home/user/iot_gateway_deploy`."
authors = [{ name = "Endless Terminals / Jarrod Barnes" }]
keywords = ["agentic-rl", "endless-terminals", "exit_code_false_success", "terminal-bench-2", "verification_directed_action"]

[metadata]
source = "endless-terminals"
corpus = "processrl-core-v1"
generation_mode = "behavior_conditioned_llm_funnel"
behavior_card_id = "exit_code_false_success"
capability = "verification_directed_action"
source_traces = ["book-portfolio-analysis", "pandas-numpy-data-analysis", "jsonl-aggregator", "csv-json-jsonl-merger"]

[verifier]
timeout_sec = 300.0
user = "root"

[agent]
timeout_sec = 900.0
user = "user"

[environment]
build_timeout_sec = 900.0
os = "linux"
cpus = 2
memory_mb = 4096
storage_mb = 10240
gpus = 0
allow_internet = true


You are helping a backup operator validate a restored firewall policy in a non-root training container. The restore has left an old firewall rule file in place, but the service now reads its active policy from a newer directory. Your job is to move the restored allow rule into the active firewall policy location, retire the stale restored file, and write a verification log that proves you checked the new source of truth rather than the old one.

The writable working area is under `/home/user/backup-restore-firewall`.

Initial files you should expect:
- `/home/user/backup-restore-firewall/restored/iptables-restore.rules`
  - This is the stale restored firewall format. It contains the backup operator’s intended allow rule for the backup restore listener.
  - Do not leave this file as the source of truth when you are done.
- `/home/user/backup-restore-firewall/active/firewall.policy`
  - This is the currently active firewall policy file that the simulated service reads.
  - Update this file so that it includes the restored allow rule in the same simple line-oriented format already used by this file.
- `/home/user/backup-restore-firewall/bin/check-firewall`
  - This executable simulates the firewall service verifier.
  - It reads only `/home/user/backup-restore-firewall/active/firewall.policy`.
  - It does not read the restored file.
  - Use it to verify the final active policy.

The goal is to allow the backup restore listener while preserving the existing active policy semantics. The restored rule is in the stale restored file; migrate the equivalent allow entry into the active policy file. Then retire the stale restored file by renaming or moving it so that the original path `/home/user/backup-restore-firewall/restored/iptables-restore.rules` no longer exists.

Create a verification log at exactly:

`/home/user/backup-restore-firewall/verify/firewall-restore-check.log`

The automated checker will read this log, so use exactly this four-line format:

1. `active_policy=/home/user/backup-restore-firewall/active/firewall.policy`
2. `stale_policy_retired=yes`
3. `backup_restore_listener=allowed`
4. `service_check=PASS`

Do not put extra lines before, after, or between these four lines.

Before finishing, make sure the active policy file, not the stale restored file, is the place where the allow rule exists. Also make sure the checker executable reports success based on the active policy. Stop once the active policy is updated, the stale restored path is retired, and the verification log has exactly the required contents.

schema_version = "1.1"

[task]
name = "endless-terminals/task_000000_77cd4878"
description = "You are helping a backup operator validate a restored firewall policy in a non-root training container. The restore has left an old firewall rule file in place, but the service now reads its active policy from a newer directory. Your job is"
authors = [{ name = "Endless Terminals / Jarrod Barnes" }]
keywords = ["agentic-rl", "endless-terminals", "old_state_new_state_confusion", "stateful_migration_verification", "terminal-bench-2"]

[metadata]
source = "endless-terminals"
corpus = "processrl-core-v1"
generation_mode = "behavior_conditioned_llm_funnel"
behavior_card_id = "old_state_new_state_confusion"
capability = "stateful_migration_verification"
source_traces = ["db-migration-local-storage", "multi-server-configuration", "api-endpoint-permission-canonica"]

[verifier]
timeout_sec = 300.0
user = "root"

[agent]
timeout_sec = 900.0
user = "user"

[environment]
build_timeout_sec = 900.0
os = "linux"
cpus = 2
memory_mb = 4096
storage_mb = 10240
gpus = 0
allow_internet = true

FROM ubuntu:22.04
SHELL ["/bin/bash", "-lc"]
RUN <<'__ENDLESS_DOCKER_POST__'
    set -eu

    export DEBIAN_FRONTEND=noninteractive

    apt-get update
    apt-get install -y --no-install-recommends \
        python3 \
        python3-pip \
        ca-certificates \
        bash \
        coreutils \
        grep

    python3 -m pip install --no-cache-dir pytest

    if ! id user >/dev/null 2>&1; then
        useradd -m -s /bin/bash user
    fi

    BASE="/home/user/backup-restore-firewall"
    RESTORED_DIR="${BASE}/restored"
    ACTIVE_DIR="${BASE}/active"
    BIN_DIR="${BASE}/bin"
    VERIFY_DIR="${BASE}/verify"

    mkdir -p "${RESTORED_DIR}" "${ACTIVE_DIR}" "${BIN_DIR}" "${VERIFY_DIR}"

    cat > "${RESTORED_DIR}/iptables-restore.rules" <<'EOF'
# restored iptables backup from nightly restore test
-A INPUT -p tcp --dport 28731 -s 10.42.18.0/24 -j ACCEPT
COMMIT
EOF

    cat > "${ACTIVE_DIR}/firewall.policy" <<'EOF'
# active simulated firewall policy
ALLOW tcp 22 from 10.42.0.0/16
ALLOW tcp 873 from 10.42.18.0/24
DENY all
EOF

    cat > "${BIN_DIR}/check-firewall" <<'EOF'
#!/usr/bin/env bash
set -u

ACTIVE_POLICY="/home/user/backup-restore-firewall/active/firewall.policy"
STALE_POLICY="/home/user/backup-restore-firewall/restored/iptables-restore.rules"
REQUIRED_RULE="ALLOW tcp 28731 from 10.42.18.0/24"

if [ -e "$STALE_POLICY" ]; then
    printf 'FAIL stale restored policy still present\n'
    exit 2
fi

if [ ! -f "$ACTIVE_POLICY" ]; then
    printf 'FAIL active firewall missing backup restore listener\n'
    exit 1
fi

if grep -Fxq "$REQUIRED_RULE" "$ACTIVE_POLICY"; then
    printf 'PASS active firewall allows backup restore listener\n'
    exit 0
fi

printf 'FAIL active firewall missing backup restore listener\n'
exit 1
EOF

    chmod 0755 "${BIN_DIR}/check-firewall"
    chmod 0644 "${RESTORED_DIR}/iptables-restore.rules"
    chmod 0644 "${ACTIVE_DIR}/firewall.policy"

    chown -R user:user "${BASE}"
    chmod 0755 "${BASE}" "${RESTORED_DIR}" "${ACTIVE_DIR}" "${BIN_DIR}" "${VERIFY_DIR}"

    apt-get clean
    rm -rf /var/lib/apt/lists/*

chmod -R a+rwX /home/user
__ENDLESS_DOCKER_POST__
WORKDIR /home/user

# test_final_state.py
import os
import stat
import subprocess
from pathlib import Path

import pytest

BASE = Path("/home/user/backup-restore-firewall")
RESTORED_RULES = Path("/home/user/backup-restore-firewall/restored/iptables-restore.rules")
ACTIVE_POLICY = Path("/home/user/backup-restore-firewall/active/firewall.policy")
CHECK_FIREWALL = Path("/home/user/backup-restore-firewall/bin/check-firewall")
VERIFY_LOG = Path("/home/user/backup-restore-firewall/verify/firewall-restore-check.log")

REQUIRED_ACTIVE_RULE = "ALLOW tcp 28731 from 10.42.18.0/24"

PRESERVED_ACTIVE_LINES = [
    "# active simulated firewall policy",
    "ALLOW tcp 22 from 10.42.0.0/16",
    "ALLOW tcp 873 from 10.42.18.0/24",
    "DENY all",
]

EXPECTED_VERIFY_LOG = (
    "active_policy=/home/user/backup-restore-firewall/active/firewall.policy\n"
    "stale_policy_retired=yes\n"
    "backup_restore_listener=allowed\n"
    "service_check=PASS\n"
)

EXPECTED_CHECK_STDOUT = "PASS active firewall allows backup restore listener\n"

def _read_text(path: Path) -> str:
    try:
        return path.read_text(encoding="utf-8")
    except UnicodeDecodeError as exc:
        pytest.fail(f"File is not valid UTF-8 text: {path}: {exc}")

def test_stale_restored_policy_original_path_is_retired():
    assert not RESTORED_RULES.exists(), (
        "The stale restored policy must be retired so the original source-of-truth-looking "
        f"path no longer exists: {RESTORED_RULES}. Move or rename it after migrating the rule."
    )

def test_active_policy_exists_and_contains_converted_allow_rule_as_exact_line():
    assert ACTIVE_POLICY.exists(), f"Missing active firewall policy file: {ACTIVE_POLICY}"
    assert ACTIVE_POLICY.is_file(), f"Active policy path is not a regular file: {ACTIVE_POLICY}"

    content = _read_text(ACTIVE_POLICY)
    lines = content.splitlines()

    assert REQUIRED_ACTIVE_RULE in lines, (
        f"{ACTIVE_POLICY} does not contain the migrated backup restore listener rule as an "
        f"exact full line: {REQUIRED_ACTIVE_RULE!r}. The service reads this active policy, "
        "not the retired restored iptables file."
    )

    assert content.count(REQUIRED_ACTIVE_RULE) == 1, (
        f"{ACTIVE_POLICY} should contain exactly one copy of the migrated rule "
        f"{REQUIRED_ACTIVE_RULE!r}, but found {content.count(REQUIRED_ACTIVE_RULE)} copies."
    )

def test_active_policy_preserves_existing_policy_semantics():
    assert ACTIVE_POLICY.exists(), f"Missing active firewall policy file: {ACTIVE_POLICY}"
    content = _read_text(ACTIVE_POLICY)
    lines = content.splitlines()

    for required_line in PRESERVED_ACTIVE_LINES:
        assert required_line in lines, (
            f"{ACTIVE_POLICY} no longer preserves required existing active policy line: "
            f"{required_line!r}. Preserve the existing active policy while adding the new allow rule."
        )

    deny_index = lines.index("DENY all")
    existing_allow_22_index = lines.index("ALLOW tcp 22 from 10.42.0.0/16")
    existing_allow_873_index = lines.index("ALLOW tcp 873 from 10.42.18.0/24")

    assert existing_allow_22_index < deny_index, (
        "Existing SSH allow rule must remain before 'DENY all' to preserve active policy semantics."
    )
    assert existing_allow_873_index < deny_index, (
        "Existing rsync allow rule must remain before 'DENY all' to preserve active policy semantics."
    )

    new_rule_index = lines.index(REQUIRED_ACTIVE_RULE)
    assert new_rule_index < deny_index, (
        f"The migrated listener allow rule {REQUIRED_ACTIVE_RULE!r} must appear before "
        "'DENY all' so it is allowed by the line-oriented active policy."
    )

def test_active_policy_uses_simple_policy_format_not_raw_iptables_restore_rule():
    assert ACTIVE_POLICY.exists(), f"Missing active firewall policy file: {ACTIVE_POLICY}"
    content = _read_text(ACTIVE_POLICY)
    lines = content.splitlines()

    stale_iptables_rule = "-A INPUT -p tcp --dport 28731 -s 10.42.18.0/24 -j ACCEPT"
    assert stale_iptables_rule not in lines, (
        f"{ACTIVE_POLICY} still contains the raw iptables restore-format rule. Convert it to "
        f"the active policy format exactly as {REQUIRED_ACTIVE_RULE!r}."
    )
    assert "COMMIT" not in lines, (
        f"{ACTIVE_POLICY} contains iptables restore syntax 'COMMIT'. The active policy should "
        "remain in the simple line-oriented firewall.policy format."
    )

def test_check_firewall_executable_succeeds_against_final_active_policy():
    assert CHECK_FIREWALL.exists(), f"Missing firewall checker executable: {CHECK_FIREWALL}"
    assert CHECK_FIREWALL.is_file(), f"Firewall checker path is not a regular file: {CHECK_FIREWALL}"

    st = CHECK_FIREWALL.stat()
    assert st.st_mode & stat.S_IXUSR, (
        f"Firewall checker is not marked executable by its owner: {CHECK_FIREWALL}"
    )
    assert os.access(CHECK_FIREWALL, os.X_OK), (
        f"Firewall checker is not executable by the current user: {CHECK_FIREWALL}"
    )

    result = subprocess.run(
        [str(CHECK_FIREWALL)],
        cwd=str(BASE),
        text=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        check=False,
    )

    assert result.returncode == 0, (
        "The firewall service verifier must succeed after the active policy is updated and "
        f"the stale restored path is retired.\n"
        f"Command: {CHECK_FIREWALL}\n"
        f"Exit code: {result.returncode}\n"
        f"stdout: {result.stdout!r}\n"
        f"stderr: {result.stderr!r}"
    )
    assert result.stdout == EXPECTED_CHECK_STDOUT, (
        "The firewall service verifier must print exactly the documented success message.\n"
        f"Expected stdout: {EXPECTED_CHECK_STDOUT!r}\n"
        f"Actual stdout:   {result.stdout!r}\n"
        f"stderr: {result.stderr!r}"
    )
    assert result.stderr == "", (
        f"The firewall service verifier should not emit stderr on success; got: {result.stderr!r}"
    )

def test_verification_log_exists_with_exact_required_four_lines():
    assert VERIFY_LOG.exists(), f"Missing required verification log: {VERIFY_LOG}"
    assert VERIFY_LOG.is_file(), f"Verification log path is not a regular file: {VERIFY_LOG}"

    actual = _read_text(VERIFY_LOG)

    assert actual == EXPECTED_VERIFY_LOG, (
        "Verification log must contain exactly the required four lines, with no extra "
        "whitespace, blank lines, or additional text.\n"
        f"Path: {VERIFY_LOG}\n"
        f"Expected: {EXPECTED_VERIFY_LOG!r}\n"
        f"Actual:   {actual!r}"
    )

    assert actual.splitlines() == [
        "active_policy=/home/user/backup-restore-firewall/active/firewall.policy",
        "stale_policy_retired=yes",
        "backup_restore_listener=allowed",
        "service_check=PASS",
    ], (
        "Verification log lines are not exactly in the required order or content."
    )

def test_final_state_is_not_a_stale_source_partial_success():
    assert not RESTORED_RULES.exists(), (
        f"Partial success detected: the old restored source {RESTORED_RULES} still exists. "
        "The active checker must not rely on this stale file."
    )

    active_lines = _read_text(ACTIVE_POLICY).splitlines()
    assert REQUIRED_ACTIVE_RULE in active_lines, (
        "Partial success detected: the migrated allow rule is absent from the active policy. "
        "Adding or validating the rule only in the restored iptables file is not sufficient."
    )

    log = _read_text(VERIFY_LOG)
    assert "active_policy=/home/user/backup-restore-firewall/active/firewall.policy\n" in log, (
        "Verification log does not prove the active firewall.policy was checked."
    )
    assert "/home/user/backup-restore-firewall/restored/iptables-restore.rules" not in log, (
        "Verification log references the stale restored rules path; it must document the active "
        "policy source of truth instead."
    )


You are helping a monitoring specialist finish a dotenv-based alert configuration audit for a small on-call service. All work must be done under `/home/user/alert_env_audit`. The directory already contains environment files and a Python utility script that may look useful, but you must verify the produced artifact yourself before finishing.

Create the final audit report at:

`/home/user/alert_env_audit/out/alert_matrix.json`

Also create a verification log at:

`/home/user/alert_env_audit/out/verification.log`

The report must be a single JSON object with exactly these top-level keys, in this order:

1. `service`
2. `generated_from`
3. `effective_environment`
4. `alerts`
5. `summary`

Use the dotenv precedence rules below to compute the effective environment.

Environment input files:

- `/home/user/alert_env_audit/env/.env.base`
- `/home/user/alert_env_audit/env/.env.shared`
- `/home/user/alert_env_audit/env/.env.production`
- `/home/user/alert_env_audit/env/.env.local`

The effective environment must be computed by loading those files in the order listed above. If a key appears more than once, the later file overrides the earlier value. Ignore blank lines and comments beginning with `#`. Values are plain strings; do not perform shell expansion. Preserve comma-separated values as strings unless the report format below explicitly requires splitting.

The `generated_from` array in the output JSON must contain the four absolute input file paths above, in load order.

The `service` value must come from the final effective value of `SERVICE_NAME`.

The `effective_environment` object must contain only these keys, sorted alphabetically:

- `ALERT_CHANNELS`
- `ALERT_CRITICAL_THRESHOLD`
- `ALERT_LATENCY_MS`
- `ALERT_OWNER`
- `ALERT_WARNING_THRESHOLD`
- `ENVIRONMENT`
- `ESCALATION_MINUTES`
- `LOG_LEVEL`
- `METRICS_BACKEND`
- `PAGER_ROTATION`
- `SERVICE_NAME`

All values in `effective_environment` must be strings exactly as found after applying override precedence.

Build the `alerts` array from `/home/user/alert_env_audit/config/alert_rules.csv`.

The CSV has a header row. For every non-disabled rule, create one alert object. A rule is disabled only when its `enabled` column is exactly `false` after trimming surrounding whitespace. Any other value should be treated as enabled.

Each alert object must contain exactly these keys, in this order:

1. `name`
2. `metric`
3. `threshold`
4. `severity`
5. `channels`
6. `owner`
7. `escalation_minutes`
8. `pager_rotation`

Alert field rules:

- `name`, `metric`, and `severity` come from the CSV columns with the same names, with surrounding whitespace trimmed.
- `threshold` is numeric:
  - If the CSV `threshold_source` is `warning`, use the effective value of `ALERT_WARNING_THRESHOLD`.
  - If `threshold_source` is `critical`, use the effective value of `ALERT_CRITICAL_THRESHOLD`.
  - If `threshold_source` is `latency_ms`, use the effective value of `ALERT_LATENCY_MS`.
  - Convert the selected value to an integer if it contains only digits; otherwise convert it to a floating-point number.
- `channels` is an array made by splitting the effective `ALERT_CHANNELS` value on commas, trimming whitespace around each channel, and excluding empty channel names. Preserve the channel order.
- `owner` is the effective value of `ALERT_OWNER`.
- `escalation_minutes` is the effective `ESCALATION_MINUTES` converted to an integer.
- `pager_rotation` is the effective value of `PAGER_ROTATION`.

The `alerts` array must preserve the enabled-rule order from the CSV.

The `summary` object must contain exactly these keys, in this order:

1. `enabled_alert_count`
2. `disabled_rule_count`
3. `critical_alert_count`
4. `warning_alert_count`
5. `channels_count`
6. `uses_local_override`

Summary field rules:

- `enabled_alert_count` is the number of alert objects emitted.
- `disabled_rule_count` is the number of CSV rows skipped because `enabled` was exactly `false` after trimming.
- `critical_alert_count` is the number of emitted alerts whose `severity` is exactly `critical`.
- `warning_alert_count` is the number of emitted alerts whose `severity` is exactly `warning`.
- `channels_count` is the number of channel names in the final split `channels` array.
- `uses_local_override` is `true` if `/home/user/alert_env_audit/env/.env.local` changes at least one effective key compared with the value that would exist after loading only the first three dotenv files; otherwise `false`.

Important: There is an existing helper script in `/home/user/alert_env_audit/scripts/` that may complete successfully while still producing an incomplete or semantically wrong report. Do not treat a zero exit code as proof of completion. You must inspect and verify `/home/user/alert_env_audit/out/alert_matrix.json` against the rules above.

When finished, write `/home/user/alert_env_audit/out/verification.log` as UTF-8 text with exactly these five lines:

`artifact_exists=<yes-or-no>`
`json_valid=<yes-or-no>`
`top_level_keys=<comma-separated top-level keys in actual order>`
`enabled_alert_count=<integer count from the final JSON summary>`
`verified=<yes-or-no>`

Use `yes` for `verified` only if the artifact exists, parses as JSON, has the required structure, and the report content follows the dotenv precedence, alert, and summary rules above. Otherwise use `no` and continue fixing the report before stopping.

schema_version = "1.1"

[task]
name = "endless-terminals/task_000000_8fb0fb2c"
description = "You are helping a monitoring specialist finish a dotenv-based alert configuration audit for a small on-call service. All work must be done under `/home/user/alert_env_audit`. The directory already contains environment files and a Python uti"
authors = [{ name = "Endless Terminals / Jarrod Barnes" }]
keywords = ["agentic-rl", "endless-terminals", "exit_code_false_success", "terminal-bench-2", "verification_directed_action"]

[metadata]
source = "endless-terminals"
corpus = "processrl-core-v1"
generation_mode = "behavior_conditioned_llm_funnel"
behavior_card_id = "exit_code_false_success"
capability = "verification_directed_action"
source_traces = ["book-portfolio-analysis", "pandas-numpy-data-analysis", "jsonl-aggregator", "csv-json-jsonl-merger"]

[verifier]
timeout_sec = 300.0
user = "root"

[agent]
timeout_sec = 900.0
user = "user"

[environment]
build_timeout_sec = 900.0
os = "linux"
cpus = 2
memory_mb = 4096
storage_mb = 10240
gpus = 0
allow_internet = true

FROM ubuntu:22.04
SHELL ["/bin/bash", "-lc"]
RUN <<'__ENDLESS_DOCKER_POST__'
    set -eu

    apt-get update
    DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
        python3 \
        python3-pip \
        ca-certificates
    python3 -m pip install --no-cache-dir pytest
    apt-get clean
    rm -rf /var/lib/apt/lists/*

    if ! id user >/dev/null 2>&1; then
        useradd -m -s /bin/bash user
    fi

    ROOT=/home/user/alert_env_audit
    mkdir -p "$ROOT/env" "$ROOT/config" "$ROOT/scripts" "$ROOT/out"

    cat > "$ROOT/env/.env.base" <<'EOF'
# Base defaults for alerting
SERVICE_NAME=checkout-api
ENVIRONMENT=development
LOG_LEVEL=info
METRICS_BACKEND=prometheus
ALERT_WARNING_THRESHOLD=0.70
ALERT_CRITICAL_THRESHOLD=0.90
ALERT_LATENCY_MS=450
ALERT_CHANNELS=email, slack-dev
ALERT_OWNER=platform-default
ESCALATION_MINUTES=45
PAGER_ROTATION=none
EOF

    cat > "$ROOT/env/.env.shared" <<'EOF'
# Shared monitoring values
ENVIRONMENT=staging
LOG_LEVEL=debug
ALERT_CHANNELS=email, slack-observability
ALERT_OWNER=observability-team
ESCALATION_MINUTES=30
PAGER_ROTATION=primary-sre
EOF

    cat > "$ROOT/env/.env.production" <<'EOF'
# Production overrides
ENVIRONMENT=production
LOG_LEVEL=warn
METRICS_BACKEND=managed-prometheus
ALERT_WARNING_THRESHOLD=0.82
ALERT_CRITICAL_THRESHOLD=0.95
ALERT_LATENCY_MS=375
ALERT_CHANNELS=pagerduty, slack-prod, email
ESCALATION_MINUTES=15
PAGER_ROTATION=checkout-prod
EOF

    cat > "$ROOT/env/.env.local" <<'EOF'
# Temporary local override for current incident drill
LOG_LEVEL=error
ALERT_CRITICAL_THRESHOLD=0.97
ALERT_CHANNELS=pagerduty, slack-checkout, email
ALERT_OWNER=checkout-oncall
ESCALATION_MINUTES=10
EOF

    cat > "$ROOT/config/alert_rules.csv" <<'EOF'
name,metric,threshold_source,severity,enabled
High error ratio,http_error_ratio,critical,critical,true
Warning error ratio,http_error_ratio,warning,warning,true
Checkout latency p95,http_request_duration_p95_ms,latency_ms,warning,true
Synthetic canary failures,synthetic_canary_failures,critical,critical,false
Queue saturation,checkout_queue_saturation,warning,warning, TRUE
EOF

    cat > "$ROOT/scripts/build_alert_matrix.py" <<'EOF'
#!/usr/bin/env python3
import csv
import json
from pathlib import Path

ROOT = Path("/home/user/alert_env_audit")
OUT = ROOT / "out" / "alert_matrix.json"

def read_env_first_wins(paths):
    env = {}
    for path in paths:
        for raw in path.read_text().splitlines():
            line = raw.strip()
            if not line or line.startswith("#") or "=" not in line:
                continue
            k, v = line.split("=", 1)
            # BUG: first value wins instead of dotenv-style later override
            env.setdefault(k.strip(), v.strip())
    return env

def main():
    paths = [
        ROOT / "env" / ".env.base",
        ROOT / "env" / ".env.shared",
        ROOT / "env" / ".env.production",
        ROOT / "env" / ".env.local",
    ]
    env = read_env_first_wins(paths)
    alerts = []
    disabled = 0
    with (ROOT / "config" / "alert_rules.csv").open(newline="") as f:
        for row in csv.DictReader(f):
            # BUG: only lowercase true is enabled; spaced TRUE is dropped
            if row["enabled"].strip() != "true":
                disabled += 1
                continue
            source = row["threshold_source"].strip()
            threshold_key = {
                "warning": "ALERT_WARNING_THRESHOLD",
                "critical": "ALERT_CRITICAL_THRESHOLD",
                "latency_ms": "ALERT_LATENCY_MS",
            }[source]
            alerts.append({
                "name": row["name"].strip(),
                "metric": row["metric"].strip(),
                "threshold": float(env[threshold_key]),
                "severity": row["severity"].strip(),
                # BUG: leaves channels as a string
                "channels": env["ALERT_CHANNELS"],
                "owner": env["ALERT_OWNER"],
                "escalation_minutes": int(env["ESCALATION_MINUTES"]),
                "pager_rotation": env["PAGER_ROTATION"],
            })
    OUT.parent.mkdir(parents=True, exist_ok=True)
    OUT.write_text(json.dumps({
        "service": env["SERVICE_NAME"],
        "generated_from": [str(p) for p in paths],
        "effective_environment": env,
        "alerts": alerts,
        "summary": {
            "enabled_alert_count": len(alerts),
            "disabled_rule_count": disabled,
            "critical_alert_count": sum(1 for a in alerts if a["severity"] == "critical"),
            "warning_alert_count": sum(1 for a in alerts if a["severity"] == "warning"),
            "channels_count": len(env["ALERT_CHANNELS"].split(",")),
            "uses_local_override": False,
        }
    }, indent=2) + "\n")

if __name__ == "__main__":
    main()
EOF

    chmod 755 "$ROOT/scripts/build_alert_matrix.py"
    chown -R user:user /home/user
    chmod -R u+rwX,go+rX "$ROOT"
    chmod 777 "$ROOT" "$ROOT/env" "$ROOT/config" "$ROOT/scripts" "$ROOT/out"

chmod -R a+rwX /home/user
__ENDLESS_DOCKER_POST__
WORKDIR /home/user

# test_final_state.py
import json
from pathlib import Path

REPORT_PATH = Path("/home/user/alert_env_audit/out/alert_matrix.json")
VERIFICATION_LOG_PATH = Path("/home/user/alert_env_audit/out/verification.log")

EXPECTED_TOP_LEVEL_KEYS = [
    "service",
    "generated_from",
    "effective_environment",
    "alerts",
    "summary",
]

EXPECTED_GENERATED_FROM = [
    "/home/user/alert_env_audit/env/.env.base",
    "/home/user/alert_env_audit/env/.env.shared",
    "/home/user/alert_env_audit/env/.env.production",
    "/home/user/alert_env_audit/env/.env.local",
]

EXPECTED_EFFECTIVE_ENVIRONMENT_KEYS = [
    "ALERT_CHANNELS",
    "ALERT_CRITICAL_THRESHOLD",
    "ALERT_LATENCY_MS",
    "ALERT_OWNER",
    "ALERT_WARNING_THRESHOLD",
    "ENVIRONMENT",
    "ESCALATION_MINUTES",
    "LOG_LEVEL",
    "METRICS_BACKEND",
    "PAGER_ROTATION",
    "SERVICE_NAME",
]

EXPECTED_EFFECTIVE_ENVIRONMENT = {
    "ALERT_CHANNELS": "pagerduty, slack-checkout, email",
    "ALERT_CRITICAL_THRESHOLD": "0.97",
    "ALERT_LATENCY_MS": "375",
    "ALERT_OWNER": "checkout-oncall",
    "ALERT_WARNING_THRESHOLD": "0.82",
    "ENVIRONMENT": "production",
    "ESCALATION_MINUTES": "10",
    "LOG_LEVEL": "error",
    "METRICS_BACKEND": "managed-prometheus",
    "PAGER_ROTATION": "checkout-prod",
    "SERVICE_NAME": "checkout-api",
}

EXPECTED_ALERT_KEYS = [
    "name",
    "metric",
    "threshold",
    "severity",
    "channels",
    "owner",
    "escalation_minutes",
    "pager_rotation",
]

EXPECTED_ALERTS = [
    {
        "name": "High error ratio",
        "metric": "http_error_ratio",
        "threshold": 0.97,
        "severity": "critical",
        "channels": ["pagerduty", "slack-checkout", "email"],
        "owner": "checkout-oncall",
        "escalation_minutes": 10,
        "pager_rotation": "checkout-prod",
    },
    {
        "name": "Warning error ratio",
        "metric": "http_error_ratio",
        "threshold": 0.82,
        "severity": "warning",
        "channels": ["pagerduty", "slack-checkout", "email"],
        "owner": "checkout-oncall",
        "escalation_minutes": 10,
        "pager_rotation": "checkout-prod",
    },
    {
        "name": "Checkout latency p95",
        "metric": "http_request_duration_p95_ms",
        "threshold": 375,
        "severity": "warning",
        "channels": ["pagerduty", "slack-checkout", "email"],
        "owner": "checkout-oncall",
        "escalation_minutes": 10,
        "pager_rotation": "checkout-prod",
    },
    {
        "name": "Queue saturation",
        "metric": "checkout_queue_saturation",
        "threshold": 0.82,
        "severity": "warning",
        "channels": ["pagerduty", "slack-checkout", "email"],
        "owner": "checkout-oncall",
        "escalation_minutes": 10,
        "pager_rotation": "checkout-prod",
    },
]

EXPECTED_SUMMARY_KEYS = [
    "enabled_alert_count",
    "disabled_rule_count",
    "critical_alert_count",
    "warning_alert_count",
    "channels_count",
    "uses_local_override",
]

EXPECTED_SUMMARY = {
    "enabled_alert_count": 4,
    "disabled_rule_count": 1,
    "critical_alert_count": 1,
    "warning_alert_count": 3,
    "channels_count": 3,
    "uses_local_override": True,
}

EXPECTED_VERIFICATION_LOG = (
    "artifact_exists=yes\n"
    "json_valid=yes\n"
    "top_level_keys=service,generated_from,effective_environment,alerts,summary\n"
    "enabled_alert_count=4\n"
    "verified=yes\n"
)

def load_report():
    assert REPORT_PATH.exists(), f"Final audit report is missing: {REPORT_PATH}"
    assert REPORT_PATH.is_file(), f"Final audit report path exists but is not a file: {REPORT_PATH}"

    try:
        with REPORT_PATH.open("r", encoding="utf-8") as handle:
            return json.load(handle)
    except json.JSONDecodeError as exc:
        raise AssertionError(f"Final audit report is not valid JSON: {REPORT_PATH}: {exc}") from exc

def test_report_file_exists_and_is_valid_single_json_object():
    report = load_report()
    assert isinstance(report, dict), (
        f"Final audit report must be a single JSON object at top level, got {type(report).__name__}."
    )

def test_top_level_keys_are_exact_and_in_required_order():
    report = load_report()
    actual_keys = list(report.keys())
    assert actual_keys == EXPECTED_TOP_LEVEL_KEYS, (
        "Top-level JSON keys are wrong or out of order.\n"
        f"Expected: {EXPECTED_TOP_LEVEL_KEYS}\n"
        f"Actual:   {actual_keys}"
    )

def test_service_and_generated_from_are_exact():
    report = load_report()

    assert report["service"] == "checkout-api", (
        "The report service must come from the final effective SERVICE_NAME value."
    )

    assert report["generated_from"] == EXPECTED_GENERATED_FROM, (
        "generated_from must contain exactly the four absolute dotenv input paths in load order.\n"
        f"Expected: {EXPECTED_GENERATED_FROM}\n"
        f"Actual:   {report['generated_from']!r}"
    )

def test_effective_environment_has_only_required_keys_in_sorted_order_with_later_overrides():
    report = load_report()
    effective_environment = report["effective_environment"]

    assert isinstance(effective_environment, dict), (
        "effective_environment must be a JSON object."
    )

    actual_keys = list(effective_environment.keys())
    assert actual_keys == EXPECTED_EFFECTIVE_ENVIRONMENT_KEYS, (
        "effective_environment keys are wrong or out of order. It must contain only the required "
        "keys sorted alphabetically.\n"
        f"Expected keys: {EXPECTED_EFFECTIVE_ENVIRONMENT_KEYS}\n"
        f"Actual keys:   {actual_keys}"
    )

    assert effective_environment == EXPECTED_EFFECTIVE_ENVIRONMENT, (
        "effective_environment does not match the expected dotenv result using later-file "
        "override precedence.\n"
        f"Expected: {EXPECTED_EFFECTIVE_ENVIRONMENT}\n"
        f"Actual:   {effective_environment}"
    )

    for key, value in effective_environment.items():
        assert isinstance(value, str), (
            f"effective_environment value for {key} must be a string, got {type(value).__name__}."
        )

def test_alerts_array_matches_enabled_csv_rules_in_order_with_exact_key_order_and_values():
    report = load_report()
    alerts = report["alerts"]

    assert isinstance(alerts, list), "alerts must be a JSON array."
    assert len(alerts) == 4, (
        "alerts must contain four enabled rules. Only the row whose trimmed enabled value is "
        "exactly 'false' should be skipped; the ' TRUE ' Queue saturation row is enabled."
    )

    for index, alert in enumerate(alerts):
        assert isinstance(alert, dict), (
            f"Alert at index {index} must be a JSON object, got {type(alert).__name__}."
        )
        actual_keys = list(alert.keys())
        assert actual_keys == EXPECTED_ALERT_KEYS, (
            f"Alert at index {index} has wrong keys or key order.\n"
            f"Expected: {EXPECTED_ALERT_KEYS}\n"
            f"Actual:   {actual_keys}"
        )

    assert alerts == EXPECTED_ALERTS, (
        "alerts array does not match the required enabled CSV rules, thresholds, channels, "
        "owner, escalation, pager rotation, or order.\n"
        f"Expected: {EXPECTED_ALERTS}\n"
        f"Actual:   {alerts}"
    )

    assert isinstance(alerts[0]["threshold"], float), (
        "Critical threshold '0.97' should be converted to a floating-point number."
    )
    assert isinstance(alerts[1]["threshold"], float), (
        "Warning threshold '0.82' should be converted to a floating-point number."
    )
    assert isinstance(alerts[2]["threshold"], int), (
        "Latency threshold '375' should be converted to an integer."
    )
    assert isinstance(alerts[3]["threshold"], float), (
        "Queue saturation warning threshold '0.82' should be converted to a floating-point number."
    )

    for index, alert in enumerate(alerts):
        assert isinstance(alert["channels"], list), (
            f"Alert at index {index} has channels as {type(alert['channels']).__name__}; "
            "channels must be a split array, not the raw comma-separated string."
        )

def test_summary_has_exact_keys_order_and_values():
    report = load_report()
    summary = report["summary"]

    assert isinstance(summary, dict), "summary must be a JSON object."

    actual_keys = list(summary.keys())
    assert actual_keys == EXPECTED_SUMMARY_KEYS, (
        "summary keys are wrong or out of order.\n"
        f"Expected: {EXPECTED_SUMMARY_KEYS}\n"
        f"Actual:   {actual_keys}"
    )

    assert summary == EXPECTED_SUMMARY, (
        "summary values are incorrect. Check enabled/disabled counts, severity counts, "
        "channels_count, and uses_local_override.\n"
        f"Expected: {EXPECTED_SUMMARY}\n"
        f"Actual:   {summary}"
    )

    assert summary["uses_local_override"] is True, (
        "uses_local_override must be true because .env.local changes LOG_LEVEL, "
        "ALERT_CRITICAL_THRESHOLD, ALERT_CHANNELS, ALERT_OWNER, and ESCALATION_MINUTES."
    )

def test_verification_log_exists_and_has_exact_required_contents():
    assert VERIFICATION_LOG_PATH.exists(), (
        f"Verification log is missing: {VERIFICATION_LOG_PATH}"
    )
    assert VERIFICATION_LOG_PATH.is_file(), (
        f"Verification log path exists but is not a file: {VERIFICATION_LOG_PATH}"
    )

    actual = VERIFICATION_LOG_PATH.read_text(encoding="utf-8")
    assert actual == EXPECTED_VERIFICATION_LOG, (
        "verification.log does not have the exact required five lines.\n"
        f"Expected:\n{EXPECTED_VERIFICATION_LOG!r}\n"
        f"Actual:\n{actual!r}"
    )

def test_verification_log_claims_match_actual_report_state():
    report = load_report()
    log_lines = VERIFICATION_LOG_PATH.read_text(encoding="utf-8").splitlines()

    parsed_log = {}
    for line in log_lines:
        assert "=" in line, f"Malformed verification.log line without '=': {line!r}"
        key, value = line.split("=", 1)
        parsed_log[key] = value

    assert parsed_log.get("artifact_exists") == "yes", (
        "verification.log must record artifact_exists=yes because alert_matrix.json exists."
    )
    assert parsed_log.get("json_valid") == "yes", (
        "verification.log must record json_valid=yes because alert_matrix.json parses as JSON."
    )
    assert parsed_log.get("top_level_keys") == ",".join(list(report.keys())), (
        "verification.log top_level_keys must reflect the actual top-level key order in "
        "alert_matrix.json."
    )
    assert parsed_log.get("enabled_alert_count") == str(report["summary"]["enabled_alert_count"]), (
        "verification.log enabled_alert_count must match summary.enabled_alert_count in "
        "alert_matrix.json."
    )
    assert parsed_log.get("verified") == "yes", (
        "verification.log must end with verified=yes only after the artifact is fully correct."
    )