昨天固定了 MCP manifest。今天把同樣的問題擴大到整個 Agent release:除了工具設定,prompt、package、container、model descriptor 與 IaC 也會影響行為。
工程司有兩份 system instructions,第二份多了「保留每個 proposal 的 source」。測試故意讓兩份都叫 1.0.0,檢查 gate 是否只看版本號,還是會重新讀取檔案與審查紀錄。
今天先用合成檔案建立發布檢查。Prompt 不會拿去執行,也不部署 Azure 資源;我們先確認內容或審查紀錄對不上時,程式能不能找出原因。
先列出發布清單中的 package 與 container,確認套件資料有對回 lockfile。

manifest 列出必要類別與固定的 digest,旁邊可以核對 package、container 的描述,以及 package_lock_bound=true。這份清單只涵蓋 manifest 已列出的類別,還不能說整條供應鏈都已盤點完成。
接著把 prompt 與 model deployment 分開,核對各自的內容指紋與來源。

畫面列出 prompt 與 model 各自的 digest、version、source path 與 license。兩者都會影響 Agent,不能只用一個籠統的版本號一起帶過;release scope 會在後面的 review descriptor 中核對。
再將 MCP manifest 與 Foundry IaC 納入同一份檢查。

畫面列出 MCP 與 IaC 各自的 kind、digest、source 與 license,讓我們先認出這次準備發布的整合與平台設定。Behavior 與 release scope 則在後面的 review descriptor 中核對。
清單列完後,先找 package 與 container 對應的審查紀錄。

畫面中的 review_records 0–1 列出審查綁定的 descriptor 與 digest,沒有顯示 reviewer 或審查時間。這裡先確認審查是否對到同一份產物的內容與描述。
再看 prompt 與 model,確認它們各自對應哪一份審查。

畫面中的 review_records 2–3 分別綁定 prompt 與 model 的描述欄位與 digest。日後更換模型或提示詞,就能回頭對照審查所綁定的版本;這張圖沒有列出 reviewer。
最後核對 MCP 與 IaC 的審查,並確認這些紀錄的來源。

review_records 4–5 綁定 MCP 與 IaC 的 descriptor、digest。這些仍是合成的審查資料,只用來測試本機流程,不能當成可信的公開發布核准。
本系列先固定六類輸入:
| 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 是否一致 |
這六類是本系列先列出的 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 相同,content digest 與 review descriptor 卻對不上,因此發布被拒絕。digest 在這裡只指出內容與審查版不同,沒有判斷新內容是否安全。
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 測的是相同版本號下的內容漂移。Gate 沒有執行 prompt,也沒有判斷新增句子是否惡意;它只確認準備發布的 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 的 descriptor 宣告 capability 為 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 會保留每一項失敗原因,方便對回哪份檔案或審查出了問題:
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、digest 格式、reviewer 與 change reference。artifact_from_file() 會讀取 fixture bytes 重算 digest,不採信 manifest 自填的值。
reviewed_descriptor_sha256 可以找出描述被修改的情況,但它仍是 repository 內部的一致性檢查。這裡沒有外部 key 替 reviewer 或決策簽章,也沒有 transparency log。因此,若有人能同時修改程式、資料與 review,光靠這份 hash 還無法確認是誰核准的。
signature_status 在 fixture 裡一律是 not_evaluated。嚴格模式只有在注入真正的 signature_verifier 後才可能通過;本機沒有 verifier,所以會回 ARTIFACT_SIGNATURE_VERIFIER_UNAVAILABLE。這不是簽章驗證示範,更不是 attestation。
接著看六類產物的失敗原因,以及公開發布仍缺少哪些條件。

畫面列出 public_release_failures、兩項 blockers 與 public_release_ready=false。Baseline public gate 沒有要求簽章;strict 檢查找不到 verifier,也不能解讀成簽章通過。合成的 review decision 同樣不能當成可信的人工核准。
再分開看簽章是否執行,以及只有合成資料時,發布檢查會不會放行。

本圖用合成 review 檢查簽章與發布條件:signature 未執行,strict 模式列出失敗原因,synthetic_only_public_readiness.ready=false。Baseline public gate 沒有要求 signature;strict 模式找不到 verifier,也不能算簽章通過。這裡只測這兩組條件,沒有可信的人工發布核准。
Lab 裡的 review、license decision 與 provenance 都是合成資料,只用來測試 gate。即使暫時清空 license failure,synthetic artifact 與 synthetic review 仍會各自阻止公開發布,所以預期結果是:
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
這裡 public 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 翻譯成可公開發布。
今天的 manifest 若寫「已部署版本 A」,還要從 Foundry 讀回 Agent version、model deployment 與實際工具設定,對照審查時綁定的 digest。Repository 裡的 JSON 即使全部一致,也只能證明提交前的內容;平台上的版本若沒讀回,release chain 中間仍是空白。
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 approval 處理某次工具呼叫是否等待核准。這些設定也要放進 release manifest,但它們沒有檢查 package、prompt、publisher 或 container 的來源可信度。
比較可追溯的 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
Foundry readiness 頁面將 Agents core 列為 GA;個別 model、tool、SDK、region 與 networking 條件仍需逐項核對。Hosted Agents 文件另有 region、session、registry 與版本限制,不能因為 Agents core 是 GA,就把每個部署組合一起視為 GA。
NIST SSDF 1.1 提供把安全活動整合進 SDLC 的高階架構;ISO/IEC 27001:2022 則把這些活動放回組織的資訊安全管理系統。本文只把 artifact inventory、review 與 release evidence 壓進一道本機 gate;供應商治理、組織層風險處理與 ISMS 稽核都不在這份 manifest 的證據範圍。
保留相同的 content digest,只換掉 descriptor 的六種欄位,再檢查拒絕原因。

content_digest_unchanged=true,六種欄位變更仍各自得到明確的 descriptor mismatch。檔案內容相同,不代表它的類別、來源或使用範圍也和審查時相同。這裡檢查的是描述是否一致,沒有判斷內容安全性。
最後走一次正常升級:改為 2.0.0,重新綁定 digest 與完整描述,再看哪些檢查已通過。

升版並重新綁定 descriptor 後,positive_path_passed=true;原本非空的 failures 可以對照出差異。License 與 synthetic review 的限制仍保留,signature verification 也沒有執行,所以這個子檢查通過後,public release 仍不放行。
從 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
鎖定環境的測試紀錄為 14 passed。驗收與畫面資料涵蓋以下案例及拒絕原因:
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 有驗收測試,但沒有收進畫面資料。第一份 JSON 包含 D13-R01/D13-A01,第二份則依序包含 D13-A02/D13-A04/D13-B01。它們保存的 observation 格式如下:
{
"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"]
}
}
}
讀結果時,把 artifact 清單、digest 比對與 public blockers 一起看。integrity_gate_passed=true 只代表其中一項檢查通過,還不能決定是否發布。畫面來自本機案例,外部與雲端呼叫沒有觀測資料。
接著比較三種變更:同樣 1.0.0 卻換了內容、digest 不變卻改了六種 descriptor 欄位,以及升到 2.0.0 後重新綁定完整描述的正常流程。
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」截圖。
NOASSERTION 表示授權資訊不足,不能直接判定侵權,也不足以允許公開發布。在這個範例裡,LICENSE-DECISION.md 沒有完成時,gate 仍會保留拒絕。內容、描述與 review 都對得上,只回答了完整性問題;授權與外部簽章還有各自的判準。
部署完成後,還要從 registry、Foundry Agent version 與 IaC deployed state 讀回實際狀態,對照 release manifest。CI 記錄推送了 digest A,尚未確認 endpoint 正在服務 A;read-back、smoke 與 rollback drill 才能補上部署後這一段。
供應鏈 gate 告訴我們哪一組內容準備上線。下一篇要面對另一個現實:版本完全正確,Agent 也可能在回答之外,透過 trace、error 或 outbox 把敏感資料留下來。