zwesty
Write-ups / Day 31 / Metadata
Day 31 · Sat, Oct 31 · Metadata

No Artifact Left Behind

Prey slaughtered

Per-artifact metadata across formats (PNG text chunks, SQLite tables, zip members, email attachments, PowerShell launch lines), building a directed graph and finding its one root-to-terminal walk, spotting a decoy cycle

STTMClaude Homebase Both

Flags are shortened to their first 8 and last 4 characters because they're unique per team.

Challenge ID
metadata-forensics-day-31-no-artifact-left-behind (+ /time_trial, /mastery)
Solved
2026-10-31: all three modes CORRECT by 09:08:18.9 ET, 8 min 9.7 s after the solver's Single Solve start (09:00:09.3). Our automated morning solver (a scheduled script on our Kali VM) stopped on the Single (new field, nothing sent); Claude built the field and solved it at 09:02:13.1, first try. The ranked Time Trial expired without a CORRECT (a zip-header crash, then 1 wrong answer); one untimed retry was CORRECT at 09:06:37.9, so there is no ranked Time Trial. The ranked Mastery was CORRECT on its first run (09:08:18.9)
Player
Zwesty (solo), with Claude Code (Opus 5.5): the automated solver started the Single and stopped; Claude wrote the field, solved the Single, and ran both timed modes

The challenge

Everything is evidence now. Reconstruct the complete execution-and-delivery chain, defend every edge with primary metadata, and identify the tempting conclusion the corpus does not support.

answer_id Question Format
d31-case-001-graph-execution-chain-artifact-ids "Determine graph.execution_chain_artifact_ids for case-001.zip." (artifact metadata-day-31-evidence.zip -> case-001.zip) json: "compact valid JSON array with exact values and order"

As on every Metadata day, the timed modes asked today's field together with earlier days' fields: 36 questions in the Time Trial, and the Mastery had 15 sets (540 answers).

Reading the prompt: what it said we'd need

Clue What it pointed to What I did about it
"Everything is evidence now" Every file in the case is a node, whatever its format The solver treats every member of the named case zip as a node, with no manifest needed
"execution-and-delivery chain" Files that launch or deliver other files Edges come from launch-style metadata: a launch queue, run.txt, mail forwarding, script launch lines
"defend every edge with primary metadata" Each edge must come from the source file's own bytes Each node's outgoing reference is read only from its own metadata, never inferred from names
"the tempting conclusion the corpus does not support" A decoy First read as the mismatched-quote script edges, then corrected to the cycle they would close (see Time Trial). Both readings give the same Single answer
"No Artifact Left Behind" (title) Account for every file All 22 files were parsed; only 4 are on the chain
"compact valid JSON array … exact values and order" (format) Ids in chain order, no spaces ["birch-bd18",…] from root to terminal

Safety

The case holds PowerShell scripts. They were read as text only, never run. All files were parsed as bytes on our Kali VM; nothing in the evidence was opened with its own application.

What was in the files

Single Solve: metadata-day-31-evidence.zip → case-001.zip, with 22 files under artifacts/, no manifest, and no file extensions. Each file's own metadata names the next file: - PNG: a tEXt Comment of next=<id>. orchard-e5bc's comment is done a1b46417: the terminal. - SQLite: launch_queue.target. - Zip: a run.txt member saying execute <id>. - Email: a base64 attachment route.txt saying forwarded to: <id> or open: <id>. - PowerShell: Invoke-Item, Start-Process, or & with '.\<id>'.

Two scripts have mismatched quotes, so as written they wouldn't run: meadow-f365 (".\summit-d467') and xenon-cdcb ('.\birch-41eb"). Without those two edges, 18 of the files form two dead-end walks. With them, the 18 close into a cycle. The only walk from a root (a node with no incoming edge) to the done terminal is birch-bd18 → nectar-1767 → bramble-e622 → orchard-e5bc.

Timed variants used the same template with more node types. The ranked Time Trial's set added JSON {"next": …} nodes and email bodies with "continue with: …"; its root's own script edge also had mismatched quotes. The retry's set ended on a SQLite file with only a summary table (no target). The per-set chains weren't recorded.

Single Solve

The automated solver: stopped, nothing sent

The solver started the Single at 09:00:09.3 ET. It sorted the question as a new field. Its parser chain couldn't tell which file the question was about (it found no files behind metadata-day-31-evidence.zip -> case-001.zip), so its best-effort answer was []. It had no stored candidates for the field and stopped before submitting. That stop was correct: [] would have been a guess.

The field (Claude)

Today's solver builds on the Day 30 Metadata chain and adds one field, graph-execution-chain-artifact-ids: - The case zip is found through the named evidence zip (the Day 30 lesson), and every member is a node named by its file name. - Each node's references come from its own metadata: PNG text chunks, SQLite launch_queue targets, zip run.txt, decoded email attachments, and script launch lines. - The answer must be exactly one root-to-done chain; anything else stops rather than sending a guess. - Static only: bytes are parsed, never executed.

Three readings were ranked, and the first was sent: 1. ["birch-bd18","nectar-1767","bramble-e622","orchard-e5bc"], root to done, with the broken-quote edges not counted 2. The longer dead-end walk from summit-d467 (14 ids, ending at xenon-cdcb) 3. The walk from birch-41eb (4 ids, ending at meadow-f365)

started by the solver; started_at 09:00:09.3 ET
deadline    none (Single Solve)
answers     1   ["birch-bd18","nectar-1767","bramble-e622","orchard-e5bc"]
result      CORRECT   flag 423ef3d6…f089   (09:02:13.1 ET, first try)

Per-step timings for the Single weren't recorded. A regression rehearsal against earlier days' puzzles passed, and the Metadata wrapper was pointed at today's solver at about 09:04, before the timed runs (the order the Day 28 miss taught).

Time Trial

The ranked run: expired, not CORRECT

Ranked, 50 s clock (09:04:10.3 to 09:05:00.3 ET).

  1. First run: stopped before submitting on "no local header at offset -1": a nested zip whose central directory points nowhere, hit in Day 1's inventory. Fix: Day 30's zip tolerance patch now covers that error too.
  2. Second run, inside the clock: reconnected to the same attempt and sent all 36.
ranked; reconnected at +30.2 s
answers     36
artifact     289 ms
solve        165 ms
submit       186 ms   (submit +30,887 ms after started_at)
result      INCORRECT   1 wrong answer: the graph field   (09:04:41 ET)

What the miss taught: that set's root script edge also had mismatched quotes. So mismatched quotes are generator noise, not the decoy; the decoy is the cycle. Counting those edges, the cycle has no root, and the Single's answer stays the same. The clock ran out at 09:05:00.3 before another run.

The untimed retry: CORRECT, not ranked

One untimed retry (09:05:28.2 ET, no deadline) after the failed ranked run.

  1. First run: crashed before submitting. That set's terminal is a SQLite file with only a summary table. Fix: a chain may also end at a sink (a node that references no case member), and SQLite targets come from any table with a target column.
  2. Rerun on the same retry attempt (a reconnect, not a second retry):
untimed retry; reconnected at +69.0 s
answers     36
artifact     384 ms
solve        177 ms
submit       598 ms   (submit +70,124 ms after started_at)
result      CORRECT   flag 07272054…d8e5   (09:06:37.9 ET)

Not ranked. The Game Master log shows no later Time Trial run, so this lane has no ranked Time Trial for Day 31.

After the fixes: the regression rehearsal (against Day 30) passed, with 3 newly solved and nothing changed; its tests 5/5 OK; Day 30's tests OK.

Mastery

CORRECT on the first run, ranked.

started_at  09:08:13.3 ET   deadline 09:16:13.3 (8 min clock)
answers     540 (15 sets × 36)
start        142 ms
artifact    1226 ms
solve       4019 ms
submit       721 ms   (submit +6,110 ms after started_at)
result      CORRECT   flag 00728868…1408   (09:08:18.9 ET)

The rule as it stands

Every member of the named case zip is a node, and each node's references come only from its own metadata (PNG text, SQLite target, zip run.txt, email attachments, JSON next, script launch lines, quotes matched or not). Roots have no incoming reference. The answer is the unique walk from a root to a sink or done; a cycle with no root is the unsupported conclusion, and anything ambiguous stops.

Time to solve

All three modes were CORRECT by 09:08:18.9 ET, 8 min 18.9 s after release: the Single at 09:02:13.1 (2 min 3.9 s after the solver's start) and the ranked Mastery in 5.57 s on its first run. The ranked Time Trial's 50 s clock expired without a CORRECT, and the untimed retry's CORRECT at 09:06:37.9 is unranked.

Takeaways

  • The "tempting conclusion" was the cycle, not the broken quotes. The first reading fit the Single and was accepted, but only a second variant showed which feature was noise. One example can't separate the two.
  • Same answer, wrong reason, still costs. The Single passed with the quote rule; the ranked Time Trial is where that rule broke.
  • A chain can end at a sink, not only at a marked terminal. The SQLite summary file had no done marker; "no reference to any case member" is the general end.
  • Old tolerance gaps surface on new days. Day 1's inventory hit a zip whose central directory points nowhere, which cost the first 30 s of a 50 s clock.
  • A 50 s ranked clock leaves no room for a fix and a rerun. Both fixes were small; the second one needed the retry.
  • Stopping beats guessing: the automated solver's [] was never sent, and the new field itself stops on anything but one unique walk.