跳到正文
DEV Community· Taylor Zhu·· 2 小時前AI 評分58

免費方案 AI 專案的證據會過期:以 TTL 建立失敗時預設阻擋的就緒檢查清單

Evidence Expires: A Fail-Closed Readiness Checklist with TTLs for Free-Tier AI Builds

AI 導讀

這份檢查清單為免費方案 AI 專案提出以 TTL 管理就緒證據的 fail-closed 流程,過期、缺失、無法讀取或未通過的證據會阻止版本晉級。文中示例為 model_pin 設 7 天 TTL、quota_headroom 設 24 小時 TTL,並提供 YAML/JSON 記錄格式及 Python 檢查器,以退出碼區分通過、需重跑和阻擋。

正文

A gate that passed last month is not a gate. It is a receipt for a system that no longer exists.

That is the failure mode this checklist targets. You wire up a readiness gate, it goes green, and three weeks later you promote a build using evidence that describes a runtime, a model default, or a quota policy that has already changed. The gate never lied. It just never expired.

What follows is a copy-paste workflow built on evidence TTLs. Every gate artifact carries an issue time and an expiry. Anything missing, unreadable, or stale blocks promotion by default.

Why a clean pass is only a snapshot

Model progress is currently outrunning release process. That gap shows up in the boring places, not in benchmarks.

  • Model defaults drift. A provider re-points an alias, and your "verified" output shape changes.
  • Free-tier policy drifts. Rate limits, allowed regions, and idle shutdown rules are not contracts.
  • Host images drift. A base image gets a patch, and your dependency set is no longer the one you tested.
  • Quota headroom drifts. The allowance you measured last week is not the allowance you have now.
  • Reviewers drift. The person who signed off has context you cannot reconstruct.

None of these events break your code. They invalidate your evidence. A readiness gate that cannot detect that is decoration.

Three outcomes, and only three

Keep the verdict vocabulary tiny so nobody has to interpret it during an incident.

  1. PASS — fresh evidence exists, it says PASS, and it is inside its TTL.
  2. ESCALATE — evidence exists but is expired or partially complete. A human must re-run the gate and re-issue the record.
  3. BLOCK — evidence is missing, unreadable, negative, or future-dated. Promotion stops with no override path in the script.

Fail-closed means the default branch is BLOCK. You do not get a PASS because a file exists. You get a PASS because a file is current.

Assign a TTL to every artifact

TTLs are policy choices, not proofs. Pick them from how fast the underlying fact can change, then write them down where reviewers can argue with them.

Gate What the evidence proves Starting TTL On expiry
model_pin Which model default your output contract was tested against 7 days Re-run prompts, re-issue record
server_image Base image digest and package set 14 days Rebuild, re-run smoke test
quota_headroom Current free allowance still fits peak usage 24 hours Re-measure before deploy window
egress_review Which hosts and credentials the build may reach 30 days Re-review, re-sign
rollback_drill You can return to the previous version 30 days Rehearse, attach log digest
cost_ceiling Expected spend, including non-free overflow 7 days Re-estimate, re-approve

If a row has no owner, delete the row. An unattributed gate becomes nobody's job within two sprints.

The checker: fail closed on stale evidence

Two files. One declares the gates and their TTLs; the other reads the evidence records and returns an exit code your CI can branch on.

Gate declarations

# gates.yaml
version: 1
gates:
  model_pin:
    evidence: .evidence/model_pin.json
    ttl_hours: 168
    owner: "@platform"
  server_image:
    evidence: .evidence/server_image.json
    ttl_hours: 336
    owner: "@platform"
  quota_headroom:
    evidence: .evidence/quota_headroom.json
    ttl_hours: 24
    owner: "@platform"
  rollback_drill:
    evidence: .evidence/rollback_drill.json
    ttl_hours: 720
    owner: "@oncall"

Evidence record format

The record is deliberately dumb: a timestamp, a result, the exact command that produced it, and a digest of the artifact. Do not store prose here; store the command.

{
  "gate": "model_pin",
  "issued_at": "2026-10-16T09:00:00Z",
  "issued_by": "platform",
  "command": "<the exact command that produced this record>",
  "digest": "sha256:REPLACE_ME",
  "result": "PASS"
}

The 60-line checker

This is a sketch, not a battle-tested tool. Confirm the paths and timestamp formats against your own repo before you wire it into a pipeline.

#!/usr/bin/env python3
"""Fail-closed gate checker.

Exit codes:
  0 = all gates PASS
  2 = at least one gate ESCALATE (stale, human re-run required)
  1 = at least one gate BLOCK (missing, unreadable, negative)
"""
import datetime as dt
import json
import sys
from pathlib import Path

import yaml  # pip install pyyaml

ROOT = Path(__file__).resolve().parent
ORDER = {"BLOCK": 0, "ESCALATE": 1, "PASS": 2}


def now_utc():
    return dt.datetime.now(dt.timezone.utc)


def parse_ts(value):
    return dt.datetime.fromisoformat(value.replace("Z", "+00:00"))


def evaluate(name, spec, clock):
    path = ROOT / spec["evidence"]
    if not path.exists():
        return "BLOCK", f"{name}: no evidence file at {spec['evidence']}"
    try:
        record = json.loads(path.read_text())
    except (json.JSONDecodeError, OSError) as exc:
        return "BLOCK", f"{name}: unreadable evidence ({exc})"
    if record.get("result") != "PASS":
        return "BLOCK", f"{name}: last result was {record.get('result')!r}"
    if not record.get("command"):
        return "BLOCK", f"{name}: record has no reproducible command"
    try:
        age_h = (clock - parse_ts(record["issued_at"])).total_seconds() / 3600
    except (KeyError, ValueError) as exc:
        return "BLOCK", f"{name}: bad issued_at ({exc})"
    if age_h < 0:
        return "BLOCK", f"{name}: evidence is dated in the future"
    ttl = float(spec["ttl_hours"])
    if age_h > ttl:
        return "ESCALATE", f"{name}: {age_h:.1f}h old, ttl {ttl:.0f}h"
    return "PASS", f"{name}: fresh ({age_h:.1f}h / {ttl:.0f}h), owner {spec.get('owner')}"


def main():
    config = yaml.safe_load((ROOT / "gates.yaml").read_text())
    clock = now_utc()
    verdicts = [evaluate(n, s, clock) for n, s in config["gates"].items()]
    for verdict, message in sorted(verdicts, key=lambda v: ORDER[v[0]]):
        print(f"[{verdict:8}] {message}")
    if any(v == "BLOCK" for v, _ in verdicts):
        return 1
    if any(v == "ESCALATE" for v, _ in verdicts):
        return 2
    return 0


if __name__ == "__main__":
    sys.exit(main())

Wiring it in

python check_gates.py
code=$?
case "$code" in
  0) echo "promotion allowed" ;;
  2) echo "re-run stale gates, then re-check" ; exit 2 ;;
  1) echo "do not promote" ; exit 1 ;;
esac

The important detail is the middle branch. Exit code 2 is not a failure of your software; it is a signal that a fact you depend on has aged out. Treat re-running as the default path, not as an exception.

Re-verification is a scheduling problem

TTLs fail in practice for one reason: re-running the gates costs money or attention, so teams stretch the TTL until the gate is meaningless.

The lever that makes the loop survivable is cheap execution. This is where MonkeyCode is relevant to the workflow: the project is open source, and it publishes a free model allowance plus a free server option. Those two make a scheduled re-verify job affordable in a way that a metered runner often is not — you can re-run a prompt-contract check, capture the evidence record, and let the TTL reset without opening a budget conversation. Confirm the current terms yourself before you depend on them; free allowances are advertised figures and they change. At the time of writing the operator advertises a 10M-token grant, which is a number to verify, not a number to bake into an SLA.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

A practical division of labor:

  • The free server option runs the scheduled re-verify job and writes the evidence record.
  • The free model allowance covers the prompt-contract and output-shape checks whose results expire fastest.
  • Your own repo stays the source of truth. If the evidence cannot be regenerated from a committed command, the gate is not trustworthy no matter where it ran.

The copy-paste checklist

Run this before every promotion. Each item is a gate, and each gate must fail closed.

  1. Does every gate in gates.yaml have an owner and a TTL?
  2. Does every evidence file exist, parse, and carry result: PASS?
  3. Does every record include the exact command that produced it?
  4. Is every record inside its TTL, and none future-dated?
  5. Can a second person reproduce one random record from scratch?
  6. Is the quota headroom check less than 24 hours old?
  7. Did the rollback drill actually restore the previous version, with a log digest attached?
  8. Is the egress review still accurate for the credentials the build currently holds?
  9. Does the promotion job block when the checker exits non-zero?
  10. Is there a named human who handles the ESCALATE branch within one working day?

Item 9 is the one teams skip. A checker that prints warnings but returns success is a dashboard, not a gate.

Limitations, and who should not use this

  • This is not runtime monitoring. TTLs catch drift between deploys; they do not detect a provider failing mid-request. You still need health checks and alerting.
  • TTLs are policy, not evidence. A seven-day TTL does not mean the fact was true for seven days — only that it was checked once within that window.
  • Records are only as good as their commands. Unreproducible evidence should be deleted, not grandfathered.
  • Free tiers carry no guarantee. Do not place regulated data, irreplaceable state, or an availability commitment on a free server option. Keep a documented path to paid or self-hosted infrastructure.

Skip this approach if your deployments are fully pinned and immutable with no third-party drift — the TTLs will just add ceremony. Skip it too if you have nowhere durable to store evidence, because expired records you cannot find are worse than no records.

The one habit that matters

Start with a single gate and a real TTL. Pick model_pin, give it seven days, and write the command that produces its evidence. When it goes stale, you will feel the friction immediately — and that friction is the point, because it is the moment you would otherwise have shipped on a fact that had already stopped being true.

If you want to try the loop without a budget, the open-source project's free server option is a reasonable place to run the first scheduled re-verify job.

來源:DEV Community · dev.to