-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 轨迹层指针协议 pointer@1 + 内网 blob 最小实现(W1-B3,IR-0006) #431
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,197 @@ | ||
| #!/usr/bin/env bash | ||
| # blob-store.sh —— 轨迹层 blob 存储·内网最小实现(IR-0006 W1-B3 / ADR-0103,AC-3d/3e) | ||
| # | ||
| # 内容寻址存储(键=sha256):git/判定层只存指针(sha256+store+retention), | ||
| # 轨迹本体留内网(宪法 §14a / INV-06"git 侧零 payload 本体"的执行面)。 | ||
| # | ||
| # 布局($BLOB_STORE_ROOT): | ||
| # objects/<sha[:2]>/<sha> blob 本体(内容寻址、不可变:同地址异内容=红) | ||
| # meta/<sha>.json 单行 JSON:{bytes,retention,sha256,stored_at,store} | ||
| # (retention 只增不减:同 blob 再入取最长保留) | ||
| # | ||
| # 子命令: | ||
| # put --file F [--retention R] → 入库(幂等去重);stdout=指针 JSON | ||
| # get --sha256 H [--out FILE] → 回取并重算 sha256 比对(AC-3e); | ||
| # 缺失/不符=exit 3,零输出(宁红勿假) | ||
| # verify --sha256 H → 在场+hash 校验(0=绿) | ||
| # sweep → 过期 blob 清除(按 meta 最长 retention) | ||
| # | ||
| # env: | ||
| # BLOB_STORE_ROOT 存储根目录(默认 /tmp/blob-store——内网部署必须显式指定) | ||
| # BLOB_STORE_NAME bucket 名(默认 evidence-hot;指针 store=self-cloud-blob://<名>) | ||
| # BLOB_STORE_NOW sweep 时钟覆盖(测试用 ISO;空=系统时钟) | ||
| # retention 词表(pointer@1):30d 90d 180d 1y 3y forever | ||
| # 退出码:0=成功 | 2=参数/环境 | 3=数据无效/缺失/篡改(fail-closed) | ||
| set -uo pipefail | ||
|
|
||
| RETENTION_RANK="30d:2592000 90d:7776000 180d:15552000 1y:31536000 3y:94608000 forever:infinity" | ||
|
|
||
| _ret_secs() { # <retention> → 秒数(空=infinity) | ||
| local r | ||
| r=$(printf '%s\n' "$RETENTION_RANK" | tr ' ' '\n' | sed -n "s/^$1://p") | ||
| [[ -n "$r" ]] || return 2 | ||
| printf '%s\n' "$r" | ||
| } | ||
|
|
||
| _iso2epoch() { date -u -d "$1" +%s 2>/dev/null; } | ||
|
|
||
| _die2() { echo "FATAL: $*" >&2; exit 2; } | ||
| _die3() { echo "FATAL: $*" >&2; exit 3; } | ||
|
|
||
| ROOT="${BLOB_STORE_ROOT:-/tmp/blob-store}" | ||
| NAME="${BLOB_STORE_NAME:-evidence-hot}" | ||
| if ! printf '%s' "$NAME" | grep -Eq '^[a-z0-9][a-z0-9-]{0,62}$'; then | ||
| _die2 "BLOB_STORE_NAME 非法: $NAME(小写字母数字连字符,≤63)" | ||
| fi | ||
| STORE="self-cloud-blob://$NAME" | ||
|
|
||
| _now_iso() { | ||
| if [[ -n "${BLOB_STORE_NOW:-}" ]]; then printf '%s\n' "$BLOB_STORE_NOW"; else date -u +%FT%TZ; fi | ||
| } | ||
|
Comment on lines
+48
to
+50
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 8. Unvalidated clock corrupts json BLOB_STORE_NOW is copied verbatim into stored_at and interpolated into JSON without validation or escaping, so an invalid value can make both metadata and the returned pointer malformed. This contradicts the documented ISO timestamp input and the pointer schema pattern. Agent Prompt
|
||
| _now_epoch() { | ||
| if [[ -n "${BLOB_STORE_NOW:-}" ]]; then _iso2epoch "$BLOB_STORE_NOW"; else date -u +%s; fi | ||
| } | ||
|
|
||
| _objpath() { printf '%s/objects/%s/%s' "$ROOT" "${1:0:2}" "$1"; } | ||
| _metapath() { printf '%s/meta/%s.json' "$ROOT" "$1"; } | ||
|
|
||
| # meta 单行 JSON 字段抽取(写入方为本脚本 printf,形态受控) | ||
| _meta_get() { # <file> <key> → 值(数字/字符串裸值) | ||
| sed -n "s/.*\"$2\":\(\"\?[^,}]*\"\?\).*/\1/p" "$1" | tr -d '"' | ||
| } | ||
|
|
||
| # ---- put ---- | ||
| cmd_put() { | ||
| local file="" retention="90d" | ||
| while [[ $# -gt 0 ]]; do | ||
| case "$1" in | ||
| --file) file="${2:?}"; shift 2 ;; | ||
| --retention) retention="${2:?}"; shift 2 ;; | ||
| *) _die2 "put 未知参数 $1" ;; | ||
| esac | ||
| done | ||
| [[ -n "$file" && -f "$file" ]] || _die2 "put --file <路径> 必填且存在" | ||
| _ret_secs "$retention" >/dev/null || _die2 "retention 非法: $retention(词表 30d/90d/180d/1y/3y/forever)" | ||
|
|
||
| local sha bytes obj meta | ||
| sha=$(sha256sum "$file" | cut -d' ' -f1) || _die3 "sha256 计算失败" | ||
| bytes=$(wc -c <"$file" | tr -d ' ') | ||
|
Comment on lines
+76
to
+78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 6. Empty put violates schema put accepts an empty regular file and emits bytes:0, but pointer@1 requires any present bytes value to be at least 1. The command therefore produces a pointer that fails its own advertised protocol. Agent Prompt
|
||
| obj=$(_objpath "$sha") | ||
| meta=$(_metapath "$sha") | ||
|
|
||
| # 不可变执法:同地址已有不同内容=红(内容寻址=同 sha 必同内容) | ||
| if [[ -f "$obj" ]]; then | ||
| local exist_sha | ||
| exist_sha=$(sha256sum "$obj" | cut -d' ' -f1) | ||
| [[ "$exist_sha" == "$sha" ]] || _die3 "对象地址冲突($obj 内容与 $sha 不符)——不可变纪律被破坏" | ||
| fi | ||
|
|
||
| # retention 只增不减:取 max(old, new)(forever=infinity 最大) | ||
| local stored_at | ||
| stored_at=$(_now_iso) | ||
| if [[ -f "$meta" ]]; then | ||
| local old_ret old_secs new_secs | ||
| old_ret=$(_meta_get "$meta" retention) || true | ||
| if [[ -n "$old_ret" ]] && _ret_secs "$old_ret" >/dev/null 2>&1; then | ||
| old_secs=$(_ret_secs "$old_ret") | ||
| new_secs=$(_ret_secs "$retention") | ||
| if [[ "$old_secs" == "infinity" ]] \ | ||
| || { [[ "$new_secs" != "infinity" ]] && [[ "$new_secs" -lt "$old_secs" ]]; }; then | ||
| retention="$old_ret" # 已有保留更长(或 forever)——不降级 | ||
| fi | ||
| stored_at=$(_meta_get "$meta" stored_at) # 起算点不重置(首次入库时刻) | ||
| fi | ||
| fi | ||
|
|
||
| mkdir -p "$(dirname "$obj")" "$(dirname "$meta")" | ||
| [[ -f "$obj" ]] || cp -- "$file" "$obj" || _die3 "对象写入失败" | ||
| printf '{"bytes":%s,"retention":"%s","sha256":"%s","stored_at":"%s","store":"%s"}\n' \ | ||
| "$bytes" "$retention" "$sha" "$stored_at" "$STORE" >"$meta" | ||
|
Comment on lines
+106
to
+109
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1. Put masks metadata failure cmd_put does not check directory creation or metadata redirection, so it can print a valid-looking pointer and exit 0 after failing to persist metadata. This occurs, for example, when the object already exists but the metadata directory is unwritable, leaving a successful pointer that sweep cannot manage. Agent Prompt
Comment on lines
+106
to
+109
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 3. Concurrent puts lose retention Two put processes can both read the same old metadata and then overwrite it independently, allowing a shorter retention update to win after a longer one. Direct copying and metadata truncation also expose partially published state to concurrent readers. Agent Prompt
Comment on lines
+106
to
+109
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 5. Mutable input breaks addressing put hashes the source file and later copies it without verifying the copied bytes, so a concurrently modified input can be stored under the old SHA while the emitted pointer still claims that SHA. Every subsequent get or verify then rejects the supposedly successful object as corrupted. Agent Prompt
Comment on lines
+92
to
+109
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift 使 retention 元数据更新具备并发原子性。 两个进程可同时读取旧的 对同一 🤖 Prompt for AI Agents |
||
|
|
||
| printf '{"bytes":%s,"retention":"%s","sha256":"%s","stored_at":"%s","store":"%s"}\n' \ | ||
| "$bytes" "$retention" "$sha" "$stored_at" "$STORE" | ||
| } | ||
|
|
||
| # ---- get(AC-3e:回取必校验,不符零输出) ---- | ||
| cmd_get() { | ||
| local sha="" out="" | ||
| while [[ $# -gt 0 ]]; do | ||
| case "$1" in | ||
| --sha256) sha="${2:?}"; shift 2 ;; | ||
| --out) out="${2:?}"; shift 2 ;; | ||
| *) _die2 "get 未知参数 $1" ;; | ||
| esac | ||
| done | ||
| printf '%s' "$sha" | grep -Eq '^[0-9a-f]{64}$' || _die2 "--sha256 须为 64 位 hex" | ||
| local obj | ||
| obj=$(_objpath "$sha") | ||
| [[ -f "$obj" ]] || _die3 "对象缺失: $sha(内网未入库或已过保留期)" | ||
|
|
||
| local real tmp | ||
| tmp=$(mktemp) | ||
| cp -- "$obj" "$tmp" || { rm -f "$tmp"; _die3 "对象读取失败"; } | ||
| real=$(sha256sum "$tmp" | cut -d' ' -f1) | ||
| if [[ "$real" != "$sha" ]]; then | ||
| rm -f "$tmp" | ||
| _die3 "回取校验不符(期望 $sha 实得 $real)——内容损坏,拒绝输出(宁红勿假)" | ||
| fi | ||
| if [[ -n "$out" ]]; then | ||
| mv -- "$tmp" "$out" || _die3 "写出失败: $out" | ||
| else | ||
| cat "$tmp"; rm -f "$tmp" | ||
| fi | ||
| } | ||
|
|
||
| # ---- verify ---- | ||
| cmd_verify() { | ||
| local sha="" | ||
| while [[ $# -gt 0 ]]; do | ||
| case "$1" in | ||
| --sha256) sha="${2:?}"; shift 2 ;; | ||
| *) _die2 "verify 未知参数 $1" ;; | ||
| esac | ||
| done | ||
| printf '%s' "$sha" | grep -Eq '^[0-9a-f]{64}$' || _die2 "--sha256 须为 64 位 hex" | ||
| local obj | ||
| obj=$(_objpath "$sha") | ||
| [[ -f "$obj" ]] || _die3 "对象缺失: $sha" | ||
| local real | ||
| real=$(sha256sum "$obj" | cut -d' ' -f1) | ||
| [[ "$real" == "$sha" ]] || _die3 "hash 不符(损坏): $sha" | ||
| echo "OK $sha(在场且校验一致)" | ||
| } | ||
|
|
||
| # ---- sweep(retention 执法:只删过期;无 meta 的孤儿对象不动——保守) ---- | ||
| cmd_sweep() { | ||
| local now meta_dir | ||
| now=$(_now_epoch) | ||
| [[ -n "$now" ]] || _die2 "时钟无效(BLOB_STORE_NOW?)" | ||
| meta_dir="$ROOT/meta" | ||
| [[ -d "$meta_dir" ]] || { echo "SWEEP 0 删除 0 保留(无 meta——空仓,幂等)"; return 0; } | ||
| local deleted=0 kept=0 f sha ret secs stored age | ||
| for f in "$meta_dir"/*.json; do | ||
| [[ -e "$f" ]] || continue | ||
| sha=$(_meta_get "$f" sha256) | ||
| ret=$(_meta_get "$f" retention) | ||
| secs=$(_ret_secs "$ret" 2>/dev/null) || { kept=$((kept+1)); continue; } # 坏 meta 保守保留 | ||
|
Comment on lines
+174
to
+176
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2. Malformed metadata escapes root sweep passes the unvalidated sha256 read from metadata into _objpath, so a value such as ../../victim makes rm target a path outside BLOB_STORE_ROOT. This also violates the documented policy of conservatively retaining bad metadata. Agent Prompt
|
||
| if [[ "$secs" == "infinity" ]]; then kept=$((kept+1)); continue; fi | ||
| stored=$(_iso2epoch "$(_meta_get "$f" stored_at)") | ||
| [[ -n "$stored" ]] || { kept=$((kept+1)); continue; } | ||
| age=$(( now - stored )) | ||
| if (( age > secs )); then | ||
| rm -f -- "$(_objpath "$sha")" "$f" | ||
| deleted=$((deleted+1)) | ||
|
Comment on lines
+181
to
+183
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 4. Sweep races active puts sweep can decide from stale metadata that an object is expired while a concurrent put upgrades its retention, then delete the newly updated object and metadata. The successful put pointer subsequently fails retrieval with “object missing.” Agent Prompt
Comment on lines
+181
to
+183
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 7. Sweep ignores deletion failure sweep ignores rm failure, increments deleted, and can remove metadata even when object removal failed. It therefore reports false success and can turn an expired managed object into a permanently retained orphan. Agent Prompt
|
||
| else | ||
| kept=$((kept+1)) | ||
| fi | ||
| done | ||
| echo "SWEEP $deleted 删除 $kept 保留" | ||
| } | ||
|
|
||
| case "${1:-}" in | ||
| put) shift; cmd_put "$@" ;; | ||
| get) shift; cmd_get "$@" ;; | ||
| verify) shift; cmd_verify "$@" ;; | ||
| sweep) shift; cmd_sweep "$@" ;; | ||
| *) sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//' >&2; exit 2 ;; | ||
| esac | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| #!/usr/bin/env bash | ||
| # test-blob-store.sh —— 轨迹层指针协议·内网最小实现自测(IR-0006 W1-B3 / 卡 #408) | ||
| # | ||
| # 覆盖(卡 AC 对应): | ||
| # AC-3d put:git 侧只见指针(sha256+store+retention),本体入库内容寻址地址; | ||
| # 指针形态对齐 standards/evidence/pointer.schema.yaml(pointer@1) | ||
| # AC-3e get:按指针回取重算 sha256 比对;不符/缺失=exit 3 零输出(宁红勿假) | ||
| # 不可变:同地址异内容=红;幂等:同内容重 put 不重复 | ||
| # retention 只增不减;sweep 只删过期(30d 过/90d 留/forever 留) | ||
| # 端到端:>4KB payload 经 put→payload_ref→判定层 append(git 零本体) | ||
| # 用法: bash governance/tests/test-blob-store.sh(gate.yml 自动纳入) | ||
| set -uo pipefail | ||
| DIR="$(cd "$(dirname "$0")/../.." && pwd)" | ||
| BLOB="$DIR/governance/blob-store.sh" | ||
| FAILS=0 | ||
| pass() { echo "PASS $1"; } | ||
| fail() { echo "FAIL $1"; FAILS=$((FAILS+1)); } | ||
|
|
||
| TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT | ||
| ROOT="$TMP/store" | ||
| export BLOB_STORE_ROOT="$ROOT" BLOB_STORE_NAME="evidence-hot" | ||
|
|
||
| # ---- AC-3d put:指针 + 内容寻址入库 ---- | ||
| printf 'trajectory-data-v1' >"$TMP/big.bin" | ||
| PTR=$(bash "$BLOB" put --file "$TMP/big.bin" --retention 90d) || fail "put 失败" | ||
| SHA=$(printf '%s' "$PTR" | sed -n 's/.*"sha256":"\([0-9a-f]*\)".*/\1/p') | ||
| [[ -f "$ROOT/objects/${SHA:0:2}/$SHA" ]] \ | ||
| && pass "AC-3d 内容寻址入库(objects/${SHA:0:2}/<sha>)" || fail "AC-3d 对象未落内容寻址地址" | ||
| cmp -s "$ROOT/objects/${SHA:0:2}/$SHA" "$TMP/big.bin" && pass "blob 本体与源一致" || fail "blob 本体损坏" | ||
|
|
||
| # 指针形态对齐 pointer@1(PyYAML 可用时按 schema 泛检,否则结构断言) | ||
| PTR_JSON="$TMP/ptr.json" SCHEMA="$DIR/standards/evidence/pointer.schema.yaml" | ||
| printf '%s\n' "$PTR" >"$PTR_JSON" | ||
| python3 - "$PTR_JSON" "$SCHEMA" <<'PYEOF' && pass "指针对齐 pointer.schema.yaml(pointer@1)" || fail "指针形态断言" | ||
| import json, re, sys | ||
| p = json.load(open(sys.argv[1], encoding="utf-8")) | ||
| try: | ||
| import yaml | ||
| sc = yaml.safe_load(open(sys.argv[2], encoding="utf-8")) | ||
| props = sc["properties"] | ||
| assert set(p) <= set(props), f"多余字段: {set(p) - set(props)}" | ||
| for k in sc["required"]: | ||
| assert k in p, f"必填缺失: {k}" | ||
| for k, v in p.items(): | ||
| d = props[k] | ||
| if "pattern" in d: | ||
| assert re.fullmatch(d["pattern"], str(v)), f"{k} 不匹配 pattern: {v!r}" | ||
| if "enum" in d: | ||
| assert v in d["enum"], f"{k} 不在词表: {v!r}" | ||
| if d.get("type") == "integer": | ||
| assert isinstance(v, int) and v >= d.get("minimum", 0), f"{k} 非法整数" | ||
| except ImportError: | ||
| assert set(p) <= {"sha256", "store", "retention", "bytes", "stored_at"}, p | ||
| assert re.fullmatch(r"[0-9a-f]{64}", p["sha256"]) | ||
| assert p["store"] == "self-cloud-blob://evidence-hot" | ||
| assert p["retention"] in {"30d", "90d", "180d", "1y", "3y", "forever"} | ||
| assert p["bytes"] == len(b"trajectory-data-v1") | ||
| assert "trajectory-data-v1" not in json.dumps(p), "指针泄漏 payload 本体(AC-3d:git 侧零本体)" | ||
| PYEOF | ||
|
|
||
| # ---- 幂等 + retention 只增不减 ---- | ||
| bash "$BLOB" put --file "$TMP/big.bin" --retention 30d >/dev/null \ | ||
| && grep -q '"retention":"90d"' "$ROOT/meta/$SHA.json" \ | ||
| && pass "幂等重 put 且 retention 只增不减(30d 不降 90d)" || fail "retention 被降级" | ||
| bash "$BLOB" put --file "$TMP/big.bin" --retention 1y >/dev/null \ | ||
| && grep -q '"retention":"1y"' "$ROOT/meta/$SHA.json" \ | ||
| && pass "retention 升级(90d→1y)" || fail "retention 未升级" | ||
| N=$(find "$ROOT/objects" -type f | wc -l | tr -d ' ') | ||
| [[ "$N" -eq 1 ]] && pass "同内容不重复入库(1 对象)" || fail "对象重复($N)" | ||
|
|
||
| # ---- 不可变执法:同地址异内容=红 ---- | ||
| CORRUPT="$ROOT/objects/${SHA:0:2}/$SHA" | ||
| printf 'tampered-content' >"$TMP/tamper.bin" | ||
| sha256sum "$CORRUPT" | cut -d' ' -f1 > /dev/null | ||
| # 直接改对象本体模拟地址冲突(内容寻址纪律破坏) | ||
| printf 'different-content' >"$CORRUPT" | ||
| bash "$BLOB" put --file "$TMP/big.bin" --retention 90d >/dev/null 2>&1 | ||
| [[ $? -eq 3 ]] && pass "同地址异内容 → exit 3(不可变执法)" || fail "不可变纪律未执法" | ||
| printf 'trajectory-data-v1' >"$CORRUPT" # 还原,供后续用例 | ||
|
|
||
| # ---- AC-3e get:校验回取 ---- | ||
| bash "$BLOB" get --sha256 "$SHA" --out "$TMP/roundtrip.bin" \ | ||
| && cmp -s "$TMP/roundtrip.bin" "$TMP/big.bin" \ | ||
| && pass "AC-3e 按指针回取一致(sha256 校验通过)" || fail "AC-3e 回取不一致" | ||
|
|
||
| # 篡改 → exit 3 零输出 | ||
| printf 'corrupted' >"$CORRUPT" | ||
| OUT=$(bash "$BLOB" get --sha256 "$SHA" 2>"$TMP/g.err"); RC=$? | ||
| [[ $RC -eq 3 && -z "$OUT" ]] && pass "AC-3e 回取校验不符 → exit 3 零输出" || fail "回取篡改未拦截(rc=$RC)" | ||
| printf 'trajectory-data-v1' >"$CORRUPT" | ||
|
|
||
| # 缺失 → exit 3 | ||
| bash "$BLOB" get --sha256 "$(printf 'a%.0s' {1..64})" >/dev/null 2>&1 | ||
| [[ $? -eq 3 ]] && pass "缺失对象 → exit 3" || fail "缺失未 fail-closed" | ||
|
|
||
| # verify | ||
| bash "$BLOB" verify --sha256 "$SHA" >/dev/null && pass "verify 绿" || fail "verify 红(应绿)" | ||
|
|
||
| # ---- sweep:只删过期 ---- | ||
| printf 'old-30d' >"$TMP/old.bin" | ||
| BLOB_STORE_NOW="2026-07-01T00:00:00Z" bash "$BLOB" put --file "$TMP/old.bin" --retention 30d >/dev/null | ||
| printf 'keep-90d' >"$TMP/keep.bin" | ||
| BLOB_STORE_NOW="2026-07-01T00:00:00Z" bash "$BLOB" put --file "$TMP/keep.bin" --retention 90d >/dev/null | ||
| printf 'keep-forever' >"$TMP/fv.bin" | ||
| BLOB_STORE_NOW="2026-07-01T00:00:00Z" bash "$BLOB" put --file "$TMP/fv.bin" --retention forever >/dev/null | ||
| BLOB_STORE_NOW="2026-08-29T00:00:00Z" bash "$BLOB" sweep | grep -q "SWEEP 1 删除 3 保留" \ | ||
| && pass "sweep 只删过期(30d 删,90d/forever/新对象 留)" || fail "sweep 语义不符" | ||
| OLD_SHA=$(sha256sum "$TMP/old.bin" | cut -d' ' -f1) | ||
| [[ ! -f "$ROOT/objects/${OLD_SHA:0:2}/$OLD_SHA" ]] && pass "过期对象已删" || fail "过期对象未删" | ||
|
|
||
| # ---- 端到端:>4KB payload 经指针进判定层(git 侧零本体) ---- | ||
| python3 - "$TMP/huge.bin" <<'PYEOF' | ||
| import sys | ||
| open(sys.argv[1], "w", encoding="utf-8").write("x" * 8192) | ||
| PYEOF | ||
| PTR2=$(bash "$BLOB" put --file "$TMP/huge.bin" --retention 180d) | ||
| SHA2=$(printf '%s' "$PTR2" | sed -n 's/.*"sha256":"\([0-9a-f]*\)".*/\1/p') | ||
| python3 - "$TMP/ev.json" "$PTR2" <<'PYEOF' | ||
| import json, sys | ||
| ptr = json.loads(sys.argv[2]) | ||
| ev = { | ||
| "ts": "2026-08-29T00:00:00Z", "kind": "gate", "action": "test-trajectory-ref", | ||
| "verdict": "pass", | ||
| "subject": {"card": "Cloudbird-Software/.github#408", "tenant": "cloudbird-internal"}, | ||
| "actor": {"identity": "test-runner", "role": "bot", "model": None}, | ||
| "payload": None, "payload_ref": {"sha256": ptr["sha256"], "store": ptr["store"], | ||
| "retention": ptr["retention"]}, | ||
| } | ||
| open(sys.argv[1], "w", encoding="utf-8").write(json.dumps(ev, ensure_ascii=False)) | ||
| PYEOF | ||
| python3 "$DIR/governance/evidence_shadow.py" append --file "$TMP/shadow.jsonl" --event-file "$TMP/ev.json" >/dev/null \ | ||
| && python3 "$DIR/governance/evidence_shadow.py" verify --file "$TMP/shadow.jsonl" >/dev/null \ | ||
| && pass "端到端:>4KB 本体走轨迹层,判定层只存指针(append+验链绿)" || fail "端到端指针链失败" | ||
| grep -q "\"sha256\":\"$SHA2\"" "$TMP/shadow.jsonl" \ | ||
| && ! grep -q 'xxxxxxxx' "$TMP/shadow.jsonl" \ | ||
| && pass "判定层记录零 payload 本体(只有 payload_ref)" || fail "判定层泄漏本体" | ||
|
|
||
| echo "----------------------------------------" | ||
| if [[ $FAILS -eq 0 ]]; then echo "test-blob-store: PASS"; exit 0; fi | ||
| echo "test-blob-store: $FAILS 处失败"; exit 1 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,9 @@ | ||
| # 统一证据账本·标准(v1) | ||
|
|
||
| > IR-0006 W1-B1 / ADR-0103。判定层记录 schema:[record.schema.yaml](record.schema.yaml) | ||
| > (`$id: cloudbird/evidence-standard/record@1`)。 | ||
| > (`$id: cloudbird/evidence-standard/record@1`);轨迹层指针协议: | ||
| > [pointer.schema.yaml](pointer.schema.yaml)(`$id: cloudbird/evidence-standard/pointer@1`, | ||
| > W1-B3)。 | ||
|
|
||
| ## 三层纪律(宪法 §14a / INV-06) | ||
|
|
||
|
|
@@ -11,6 +13,20 @@ | |
| | 轨迹层 | 云内网 blob | 大体积原始数据 | git 侧只存 `payload_ref`(sha256+store+retention,W1-B3) | | ||
| | 丢弃层 | GitHub 事件面 | transient 事件 | 不承诺持久 | | ||
|
|
||
| ## 轨迹层指针协议(W1-B3 / AC-3d/3e) | ||
|
|
||
| `payload_ref` 按指针协议 v1([pointer.schema.yaml](pointer.schema.yaml))产出: | ||
|
|
||
| - **内容寻址**:blob 键由 sha256 派生(`objects/<sha[:2]>/<sha>`);`store` 到 | ||
| bucket 级(`self-cloud-blob://<bucket>`)。同内容必同地址,幂等去重。 | ||
| - **不可变**:同地址异内容=红(fail-closed)。 | ||
| - **保留策略**:受控词表 `30d/90d/180d/1y/3y/forever`;retention 只增不减 | ||
| (同 blob 多指针取最长);`stored_at` 为起算点,重 put 不重置。 | ||
| - **回取校验**:按指针回取必须重算 sha256 比对;不符/缺失=红且零输出 | ||
| (宁红勿假,AC-3e)。 | ||
| - **内网最小实现**:`governance/blob-store.sh`(put/get/verify/sweep; | ||
| coreutils 即可运行,部署于内网服务器——判定锚点仍在 GitHub CI,INV-01/02)。 | ||
|
Comment on lines
+27
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win 更正运行时依赖说明。
As per path instructions, “ 🤖 Prompt for AI AgentsSource: Path instructions |
||
|
|
||
| ## OTel gen_ai.* 映射(字段命名对齐语义约定) | ||
|
|
||
| | 本 schema 字段 | OTel 语义约定对应 | 说明 | | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: Cloudbird-Software/.github
Length of output: 5128
🏁 Script executed:
Repository: Cloudbird-Software/.github
Length of output: 12747
生产调用必须显式设置
BLOB_STORE_ROOT。 未设置时,put会通过mkdir -p将原始 payload 写入可预测的/tmp/blob-store。脚本不检查目录所有权或权限,也不创建私有目录。共享主机上的其他账户可能读取或控制该路径。生产初始化应拒绝不安全的目录所有权和权限。🤖 Prompt for AI Agents