Day 12 把 MCP manifest 固定下來了,但一個 Agent release 從來不只包含 MCP。
大魔術熊貓工程司有兩份 system instructions:v2 多了一條「保留每個 proposal 的 source」。測試故意把 v2 bytes 仍標成 1.0.0;只看版本字串,兩者像是同一件貨。這兩份文字屬於 Agent 的系統政策,不是拿來假裝一般使用者會說的話。
同樣貼著 1.0.0,箱子裡可以完全不同。版本號是索引,不是內容證明。
今天不部署任何 Azure 資源,也不建立真的後門。我們只用合成 artifacts 重現「同版號換 bytes」,再建立一個會在證據不足時真的擋下 release 的 gate。
畫面判讀目標: 核對release manifest、Python package、container artifacts 與 package-lock binding的 canonical runtime fields。

先建立 release manifest 與核心可執行 artifacts 的 inventory。 可觀察狀態:manifest 的 required kinds/pinned digests、package/container descriptors 與 package_lock_bound=true 可核對。 Claim boundary:inventory 完整只針對 manifest 所列類別,不等於供應鏈完整。 此子畫面只涵蓋release manifest、Python package、container artifacts 與 package-lock binding。
畫面判讀目標: 核對prompt 與 model deployment 的 content digest、descriptor 與 provenance的 canonical runtime fields。

Prompt 與 model 是獨立供應鏈物件,不共用模糊版本標籤。 可觀察狀態:prompt/model artifacts 各自顯示 digest、version、source path、license 與 release scope。 Claim boundary:inventory 完整只針對 manifest 所列類別,不等於供應鏈完整。 此子畫面只涵蓋prompt 與 model deployment 的 content digest、descriptor 與 provenance。
畫面判讀目標: 核對vendor MCP manifest 與 Foundry IaC 的 descriptor binding的 canonical runtime fields。

整合與平台 artifacts 也必須進入相同 descriptor contract。 可觀察狀態:MCP/IaC artifacts 的 kind、behavior、digest、source、license 與 scope 可逐欄對照。 Claim boundary:inventory 完整只針對 manifest 所列類別,不等於供應鏈完整。 此子畫面只涵蓋vendor MCP manifest 與 Foundry IaC 的 descriptor binding。
畫面判讀目標: 核對package/container 的 reviewed descriptor、reviewed digest 與 reviewer metadata的 canonical runtime fields。

核心執行 artifacts 的 review 不只簽 content bytes。 可觀察狀態:review_records 0–1 顯示 review 綁定的 descriptor、digest、reviewer 與時間欄位。 Claim boundary:inventory 完整只針對 manifest 所列類別,不等於供應鏈完整。 此子畫面只涵蓋package/container 的 reviewed descriptor、reviewed digest 與 reviewer metadata。
畫面判讀目標: 核對prompt/model 的 reviewed descriptor 與 digest的 canonical runtime fields。

模型與提示詞升級各自需要可追溯 review。 可觀察狀態:review_records 2–3 分別綁定 prompt 與 model 的 descriptor fields、digest 與 reviewer。 Claim boundary:inventory 完整只針對 manifest 所列類別,不等於供應鏈完整。 此子畫面只涵蓋prompt/model 的 reviewed descriptor 與 digest。
畫面判讀目標: 核對MCP/IaC 的 reviewed descriptor、digest 與 synthetic review boundary的 canonical runtime fields。

整合層 review 完成仍不等於 public release attestation。 可觀察狀態:review_records 4–5 顯示 MCP/IaC descriptor 與 digest 綁定,review provenance 保持 synthetic。 Claim boundary:inventory 完整只針對 manifest 所列類別,不等於供應鏈完整。 此子畫面只涵蓋MCP/IaC 的 reviewed descriptor、digest 與 synthetic review boundary。
本系列先固定六類輸入:
| Kind | 大魔術熊貓工程司的 fixture | Gate 要回答的問題 |
|---|---|---|
package |
resolved dependency slice | exact version、dependency edge 與 hash 是否可重建 |
container |
合成 Containerfile |
必要文字 marker 是否存在,檔案 digest 是否一致 |
prompt |
Agent instructions | 送入 Agent version 的文字是否就是審查版 |
model |
deployment metadata | provider、deployment alias、model ID/version/region 是否改變 |
mcp |
Day 12 manifest | endpoint、description、schema、allowlist、approval 是否一致 |
iac |
合成 Bicep | 必要文字 marker 是否存在,檔案 digest 是否一致 |
這不是宣稱所有 Agent 專案都只能有六類 artifact,而是這個系列的最小 release inventory。實際專案還可能加入資料集、evaluator、policy bundle、feature flag、frontend 或資料庫 migration。
package-lock.json 是從整份 uv.lock 節錄出的十個 package fixture,不是另一份可以
自由發明版本的清單。Builder 會把當日 snapshot 的 uv.lock SHA-256 寫入source_snapshot_sha256;驗收再用 tomllib 逐項核對十筆 name/version、dependency
names 與 fixture hashes 都存在於同一份 lock。只把 lock digest 抄進 JSON、但裡面的
package rows 對不上,仍會得到 PACKAGE_INVENTORY_LOCK_CONTENT_MISMATCH。
傳統軟體供應鏈已經會遇到 movable container tag、transitive dependency 重新解析、CI artifact 被替換與 provenance 不完整。Agent 再多了幾個會直接改變行為的輸入:
BadAgent 是 ACL 2024 論文,研究如何透過 fine-tuning data 在 LLM Agent 植入並觸發後門。它提醒我們:內容相同與行為安全是兩個不同問題。不過 Day 13 的程式沒有實作模型後門掃描或 clean/trigger behavior regression;今天只回答兩個較窄、可由 repository fixture 驗證的問題:準備發布的檔案 digest 是否符合 pin,以及 review 是否綁定同一份 typed descriptor。這個 descriptor 會記錄預期安全語意,但不會實測模型是否真的照著宣告行為。
畫面判讀目標: 看見 version label 不變時舊 review 如何被錯誤沿用。

版本字串不能替 bytes 與 descriptor 背書。 可觀察狀態:version 相同、content digest drift、review descriptor mismatch、release deny。 Claim boundary:digest 不判斷新內容是否安全,只證明與 reviewed bytes 不同。
D13-A02 保留 prompt.version="1.0.0",內容卻換成第二份 system-instructions fixture。實際差異是:
v1:
Return tool proposals only.
v2 bytes, deliberately relabeled as 1.0.0:
Return tool proposals only and preserve the source of every proposal.
若 gate 只比對 version,candidate 會通過。V2 會從 fixture bytes 重算 content digest,預期結果是:
old_version=1.0.0
candidate_version=1.0.0
content_changed=true
ARTIFACT_DIGEST_DRIFT
ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:content_digest
ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:source_path
ARTIFACT_REVIEW_DIGEST_MISMATCH
這個 fixture 只證明 same-version content drift 會被 gate 擋下;它沒有執行 prompt,也沒有證明 v2 文字是惡意內容。把「不同 bytes」直接寫成「已發現後門」會比版本字串更會騙人。
Review fixture schema 現在是 2.0。每筆 ArtifactReview 不再只保存一個reviewed_digest,而是保存完整的 reviewed_descriptor 與reviewed_descriptor_sha256。Descriptor 的 canonical JSON 明確包含:
descriptor_schema、artifact_name、kind 與 version;content_digest、license_id、source_path 與 release_scope;synthetic provenance;behavior:contract_id、capability 與 side_effect_boundary。例如 prompt 的宣告是 propose-expense-actions,副作用邊界是proposal-only-no-tool-execution。這是 reviewer 要審查的預期契約,不是程式從模型
輸出推導出的實證行為。
D13-A04 專門固定 prompt bytes 與 content digest,只替換 descriptor metadata。六個
adversarial cases 都必須 fail closed:
| 被替換的欄位 | 同一 content digest 下的 reason code |
|---|---|
kind |
ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:kind |
version |
ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:version |
license_id |
ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:license_id |
source_path |
ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:source_path |
release_scope |
ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:release_scope |
behavior |
ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:behavior |
測試還會把被替換的 review descriptor 重新雜湊,證明「hash 格式正確」仍不能讓它替
另一份候選 descriptor 背書;若只改 descriptor 卻不重算 hash,則另得到ARTIFACT_REVIEW_DESCRIPTOR_DIGEST_MISMATCH。
Gate 不用一個模糊的 passed=true,而是回傳目前確實有實作的理由:
per-kind content contract
-> artifact digest pin
-> typed descriptor hash + descriptor-bound review
-> license allowlist
-> optional signature verifier
-> required artifact kinds
核心比對可以寫成:
import hashlib
import json
def descriptor_sha256(descriptor):
canonical = json.dumps(
descriptor.as_dict(),
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
return f"sha256:{hashlib.sha256(canonical).hexdigest()}"
def integrity_and_review_failures(artifact, pinned_digests, reviews):
failures = []
expected = pinned_digests.get(artifact.name)
if expected is None:
failures.append("ARTIFACT_NOT_PINNED")
elif artifact.digest != expected:
failures.append("ARTIFACT_DIGEST_DRIFT")
review = reviews.get(artifact.name)
if review is None:
failures.append("ARTIFACT_REVIEW_REQUIRED")
return failures
descriptor = review.reviewed_descriptor
if review.reviewed_descriptor_sha256 != descriptor_sha256(descriptor):
failures.append("ARTIFACT_REVIEW_DESCRIPTOR_DIGEST_MISMATCH")
candidate = artifact.review_descriptor()
for field in (
"kind",
"version",
"content_digest",
"license_id",
"source_path",
"release_scope",
"behavior",
):
if getattr(descriptor, field) != getattr(candidate, field):
failures.append(f"ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:{field}")
if review.decision != "approved":
failures.append("ARTIFACT_REVIEW_NOT_APPROVED")
return failures
實際方法是 ArtifactGate._integrity_and_review_failures();它比較的欄位還包含 descriptor
schema、artifact name 與 synthetic provenance,也保留舊的ARTIFACT_REVIEW_DIGEST_MISMATCH 作為明確 bytes reason。它另會檢查 digest 格式、
top-level review record、reviewer 與 change reference。上面只保留文章要討論的主要分支;
其中 json 與 hashlib imports 省略。檔案 digest 由 artifact_from_file() 對 fixture
bytes 重算,不採信 release manifest 自己填的值。
reviewed_descriptor_sha256 能抓到 descriptor 被部分或意外修改,卻只是 repository
內的 self-consistency check:本篇沒有外部 key 替 reviewer identity、decision 或整份
review statement 簽章,也沒有 transparency log/attestation service。因此它不能證明
「某位真人確實核准」,更不能抵抗能同時改 code、fixture 與 review 的攻擊者。
signature_status 在 fixture 裡一律是 not_evaluated。嚴格模式只有在注入真正的 signature_verifier 後才可能通過;本機沒有 verifier,所以會回 ARTIFACT_SIGNATURE_VERIFIER_UNAVAILABLE。這不是簽章驗證示範,更不是 attestation。
畫面判讀目標: 核對六類 artifact 的 strict failures、blocker codes 與 ready=false的 canonical runtime fields。

公開發布 gate 先列出實際失敗與 blockers。 可觀察狀態:public_release_failures、兩個 blockers 與 public_release_ready=false 同圖顯示。 Claim boundary:baseline public gate 未要求 signature;strict diagnostic 的 verifier unavailable 不是 signature pass。synthetic review decision 也不是可信 public approval。 此子畫面只涵蓋六類 artifact 的 strict failures、blocker codes 與 ready=false。
畫面判讀目標: 核對signature performed=false 與 synthetic-only readiness blockers的 canonical runtime fields。

本機 synthetic review 不冒充簽章或公開發布證明。 可觀察狀態:signature_claim 顯示未執行及 strict failures;synthetic_only_public_readiness.ready=false。 Claim boundary:baseline public gate 未要求 signature;strict diagnostic 的 verifier unavailable 不是 signature pass。synthetic review decision 也不是可信 public approval。 此子畫面只涵蓋signature performed=false 與 synthetic-only readiness blockers。
V2 lab 的 review、license decision 與 provenance 都是合成資料。它們可以驗證 gate
邏輯,沒有外部信任價值。就算測試把 license failure 暫時清空,synthetic artifact
與 synthetic review 仍是兩個獨立 hard blockers,不可能把public_release_ready 翻成 true。因此 public release gate 的預期結果應是:
artifact_count=6
all_content_digests_match=true
synthetic_reviews=true
package_lock_bound=true
license_id=NOASSERTION
signature_check_performed=false
public_release_blockers=[SYNTHETIC_ARTIFACT_EVIDENCE_ONLY,SYNTHETIC_REVIEW_EVIDENCE_ONLY]
public_release_ready=false
這不是「Day 13 沒做完」。相反地,release gate 最重要的驗收,就是證據不足時不要替簡報生出綠燈。
正常升級 D13-B01 會把 prompt version 明確改成 2.0.0,更新 content digest、source path 與完整 typed descriptor,再建立 synthetic review。它的 after_descriptor_bound_review=[],所以通過本機 integrity/review 子 gate;但 license 仍是 NOASSERTION,不能把 positive_path_passed=true 翻譯成可公開發布。
Microsoft Foundry 把 Agent 保存成具名、具版本的資產。官方 runtime 文件說明 Agent definition 會組合 model、instructions、tools、parameters 與可選的 safety/governance controls;Hosted Agent 每次建立 version 時,會產生包含 container image、resource allocation、environment variables 與 protocol configuration 的 immutable snapshot。Hosted Agent endpoint 一次只服務一個 version,官方文件目前不支援 version 間 traffic split。
這些平台版本很重要,但它們看不到 repository 裡的全部供應鏈證據。例如:
同理,Foundry project connection 只處理連線與 credential 使用方式,MCP require_approval 只讓特定呼叫停下來等人決定;兩者都不會證明 package、prompt、MCP publisher 或 container 本身可信。它們要成為 release manifest 裡的設定與證據,不能拿來替供應鏈 review 代班。
比較可追溯的 release chain 應該是:
source commit + resolved lock
-> package/container/prompt/model/MCP/IaC manifest
-> descriptor-bound review + license + signature evidence
-> Foundry Agent or Hosted Agent version
-> 另行建立的 benign + attack regression report
-> rollback target
截至 2026-08-03,Foundry readiness 頁面將 Agents core 列為 GA;個別 model、tool、SDK、region 與 networking 條件仍需逐項核對。Hosted Agents 文件另有 region、session、registry 與版本限制,不能因為 Agents core 是 GA,就把每個部署組合一起視為 GA。
雲端驗證:
PENDING-CLOUD
本篇沒有建立新的 Foundry Agent version、Hosted Agent version,也沒有 ACR image read-back。Release manifest 中的 model deployment JSON 與Containerfile都是 synthetic fixture,不能當成 Azure deployment attestation。
NIST SSDF 1.1 提供把安全活動整合進 SDLC 的高階架構;ISO/IEC 27001:2022 則把這些活動放回組織的資訊安全管理系統。本文只把 artifact inventory、review 與 release evidence 壓進一道本機 gate;供應商治理、組織層風險處理與 ISMS 稽核都不在這份 manifest 的證據範圍。
畫面判讀目標: 確認同一 content digest 下替換 descriptor 六個欄位仍被拒絕。

Hash 綁住 bytes;review descriptor 還要綁住 artifact identity。 可觀察狀態:content_digest_unchanged=true,六個欄位各自產生精確 descriptor mismatch reason。 Claim boundary:descriptor mismatch 只證明 candidate identity 與 reviewed descriptor 不同,不判斷內容是否安全。
畫面判讀目標: 確認 2.0.0 重新綁定 digest 與完整 descriptor 後只通過 integrity/review 子 gate。

正常升版要重新 review,也不能用 integrity pass 冒充公開可發布。 可觀察狀態:before failures 非空,descriptor 重綁後 positive_path_passed=true,但 license pending 與 synthetic review 仍在。 Claim boundary:子 gate 通過不等於 public release ready;license、synthetic evidence 與未執行 signature verification 的邊界不變。
從 repository root 執行:
cd day13
uv sync --locked
uv run pytest tests/stages/day13/test_acceptance.py -q
uv run python -m magic_panda_agent.stages.day13
2026-08-03 以 locked environment 實跑結果為 14 passed。Acceptance suite 與structured capture adapters 合起來涵蓋的 case 與 reason 包括:
D13-A01 ARTIFACT_REVIEW_REQUIRED public_ready=false
D13-A02 ARTIFACT_DIGEST_DRIFT public_ready=false
D13-A02 ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:content_digest
D13-A02 ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:source_path
D13-A02 ARTIFACT_REVIEW_DIGEST_MISMATCH public_ready=false
D13-A03 ARTIFACT_NOT_PINNED integrity_ready=false
D13-A04 ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:kind
D13-A04 ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:version
D13-A04 ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:license_id
D13-A04 ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:source_path
D13-A04 ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:release_scope
D13-A04 ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:behavior
D13-B01 after_descriptor_bound_review=[] integrity_ready=true
baseline LICENSE_NOT_APPROVED public_ready=false
strict ARTIFACT_SIGNATURE_VERIFIER_UNAVAILABLE signature_check=false
Acceptance suite 實際另外檢查:
uv.lock digest,十筆內容也逐項對回 lock;_load_release() 程式有拒絕絕對路徑與 .. 的分支,但目前 acceptance suite 沒有獨立的惡意 path regression,因此不把它列成已驗證結果。
D13-A03 由 acceptance suite 驗證,但目前structured capture projections 沒有收進該 case;不得為了版面把它假裝成 renderer 輸出。第一份 structured capture 是 D13-R01/D13-A01;第二份 structured capture 現在依序是 D13-A02/D13-A04/D13-B01,其中 observation 的 bounded shape 是:
{
"same_bytes_descriptor_tamper": {
"artifact": "expense-agent-prompt",
"content_digest_unchanged": true,
"field_failures": {
"kind": ["ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:kind"],
"version": ["ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:version"],
"license_id": ["ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:license_id"],
"source_path": ["ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:source_path"],
"release_scope": ["ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:release_scope"],
"behavior": ["ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:behavior"]
}
}
}
攻擊/before-state UI 要顯示 artifact inventory、digest match 與 public blockers。畫面如果只截 integrity_gate_passed=true,會把子 gate 冒充成正式可發布。這些 UI 圖都來自本機 scenario adapter;沒有 external/cloud observer,所以 app UI screenshot 必須標成 not observed,不能拿來推論 Foundry 的發布行為。
修補/test UI 要同時保留三件事:1.0.0 -> 1.0.0 的 content drift、同一 digest 下六種 descriptor tamper,以及 1.0.0 -> 2.0.0 後重新綁定完整 descriptor 的正常升級。
正式發布時,本篇 required app UI 圖必須由目前相關原始碼經 repository UI capture pipeline
產生。schema 3 screenshot manifest 必須以 source-tree SHA-256 與檔案數綁定實際輸入,並由
strict verifier 重算。正式圖支持畫面列出的 local release-gate 結果,不是 Azure deployment attestation,
也沒有解除 license/signature blockers。
Digest 證明 bytes 相同;簽章證明某個 key 簽過那些 bytes。它們都不能證明 signer 沒被入侵,也不能證明內容沒有惡意邏輯。Reviewer 可能誤判,synthetic review 更沒有外部信任價值。
Day 13 尚未做行為回歸。Descriptor 裡的 capability 與 side_effect_boundary 是 reviewer 要確認的宣告,不是 empirical model behavior。Digest/review gate 通過,只能說候選檔案、宣告的安全語意與審查紀錄對得上;prompt、model 或 MCP 的實際行為仍可能有問題。後續 release evidence 應另存 dataset version、randomness 設定、model deployment、成功/失敗分母與 Receipt oracle,不要只留一張「tests passed」截圖。
License 的 NOASSERTION 代表資訊不足,不等於已知侵權;正確動作是阻止 public
release,再由負責人確認 code、文章、dataset、圖片、第三方 artifact、模型條款、
notice 與散布方式。本篇不是法律意見。LICENSE-DECISION.md 尚未完成的決策仍是
硬 blocker,不能因本機 integrity 子 gate 通過而略過。Typed descriptor 已封閉
same-bytes/different-metadata 的本機反例,但沒有外部 signature/attestation;它也
沒有把「宣告的 behavior contract」升格成實際行為測量。
最後,部署後仍需要 read-back。CI 聲稱推送 digest A,不代表 endpoint 正在服務 A。正式流程要從 registry、Foundry Agent version 與 IaC deployed state 重新讀取,對回 release manifest,再執行 smoke 與 rollback drill。
以下資料均於 2026-08-03 查閱:
供應鏈 gate 告訴我們哪一組內容準備上線。下一篇要面對另一個現實:版本完全正確,Agent 也可能在回答之外,透過 trace、error 或 outbox 把敏感資料留下來。