P26-01-LEDGER: 原P26-01 全必要内容の台帳
This commit is contained in:
+385580
-713
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
+36
-39
@@ -1,64 +1,61 @@
|
||||
# P26-01 全必要内容の台帳 — 実装提出
|
||||
# P26-01 全必要内容の台帳 — 修復提出
|
||||
|
||||
対象: P26-01 / AC-P26-01。原作業「全章、7名の3ルート、全エンド、台詞、立ち絵、音声の必要枠と条件を確定する。」、原受入「原本で要求された未制作内容を欠損として記録し、架空の完成数を置かない。」を変更しない。
|
||||
対象は P26-01 / AC-P26-01。原作業「全章、7名の3ルート、全エンド、台詞、立ち絵、音声の必要枠と条件を確定する。」、原受入「原本で要求された未制作内容を欠損として記録し、架空の完成数を置かない。」を保持する。自己受入せず、30パッケージ / 88単位、正式完了15、今回加算0、partial_credit=false、whole_unit_accepted=falseを維持する。
|
||||
|
||||
本提出は内容監査資料であり、原本全体は30パッケージ・88単位、正式完了15/88のまま、今回加算0。P26-01の自己受入は行わない。独立したwhole-unit Opusレビューが原受入を判定する。
|
||||
変更は契約の5成果物のみ。TASK_SPEC、Opus選択、frozen pins、原本・AR・計画・決定・修復回数は変更しない。git書込み、他エージェント、Runner操作、native/full run、ゲーム・素材・設定・権限・API変更は行っていない。新規素材の導入はない。
|
||||
|
||||
## 作成した5成果物
|
||||
## 引継ぎと決定の版
|
||||
|
||||
|成果物|役割|
|
||||
|---|---|
|
||||
|CONTENT_LEDGER.json|原文引用、条件、状態、欠損、責任先、実ソースとSHAを持つ監査台帳|
|
||||
|CONTENT_LEDGER.md|JSONの全行と固定参照情報を欠落させない決定的な表示|
|
||||
|check_content_ledger.py|stdlibの読取専用検査、source binding、明示的な生成モード|
|
||||
|test_content_ledger.py|正例と、原本要求欠落・架空数・誤完成・出典改変等の負例|
|
||||
|IMPLEMENTATION_REPORT.md|原単位全体との対応と残存不足、本報告|
|
||||
build/owner-handoff/p26-repair3/ の ACTUAL_WHOLE_REVIEW.json、ACTUAL_OPUS_REPAIR3.json、D05_ACTUAL_HUMAN_APPROVAL.json、D05_APPROVAL_SOURCE_BINDING.json を編集前に全文読み、データとして照合した。開始時に存在した前回blocked報告の承認出典不足は、この4資料により解消している。
|
||||
|
||||
変更は契約の5 allowed_paths内だけ。TASK_SPEC、selection、frozen pins、原本、計画定義、決定、修正回数は変更しない。git書込み、他エージェント、Runner操作、ゲーム・素材編集、API/安全性/対象範囲変更は行わない。新規素材の導入はない。
|
||||
監査アンカーは f4f85bc54c39361f9efcd16f67a17835559ca70c、原計画定義ベースは 379a1ba8fd25b5ad24226178a2492f91761dc31d。この両時点ではD05の残質問は未解決として登録されていた、という歴史的事実のみを保存する。現在のD05が未回答だとは記述しない。
|
||||
|
||||
## 原単位全体の対応
|
||||
現在の実承認は a13f7c0b791dd385ff8113c3d321c0e3fe7d7c94 の docs/plans/original-completion-2026-10-02/execution/p04-01-owner-approval-20261018/D05_ACTUAL_HUMAN_APPROVAL.json、SHA256 cd365ded1b777a0327d090709f0a4161f5005562819183455d9f2e106fba3e96。チェッカーはこのgit objectのhashを照合し、4回答の原レコードを台帳に保存する。質問IDは call_5fa8987f83284eb58b685002499cb9df。破壊/完了、日付境界、停止理由、退出順序の承認であり、P04への製品採用・内容完成・P26受入ではない。D08/D09は未解決。
|
||||
|
||||
原本は `docs/traceability/original/BASELINE.txt`、SHA256 `75e4bdbdeb379c9269553f0c776bdb9cc96e01f2d6aa5ca650d132c63a8aeb1a`。各行は行番号、原文全文、ファイルSHAを保持する。
|
||||
## whole-unit対応と4修復
|
||||
|
||||
|要求|台帳ID/根拠|現状と不足の所有先|
|
||||
| 対象 | 実際の記録・修復 | 残存不足 |
|
||||
| --- | --- | --- |
|
||||
|全章|chapters.all、原本8・26〜30・256行|メインストーリーの要求のみ。章名・章数・順序・場面一覧は未定義。仕様書の第1〜14章をシナリオ章に転用しない。D08|
|
||||
|7名×3ルート|route.anri/fine/tomari/amelia/liz/mimi/satsuki × love/ntr/corruption、118〜124・30・132・138〜161行|アンリ、フィーネ、トマリ、アメリア、リズ、ミミ、サツキと純愛/NTR/堕落の正確な対応。人物固有のイベント列、入口・分岐・終点・台詞・素材・履歴/保存・通常到達は欠損。P26-02 / D08|
|
||||
|全エンド|endings.all / endings.bad / endings.game_over、9・135・72行|全エンド集合、特定バッドエンド、赤字決算3回連続のゲームオーバーを区別。個別終点ID・本数は未定義。3ルートを3個の完成エンドに数えない。D08 / P26-02|
|
||||
|台詞|dialogue.all、155・230・256行、全route行の条件|会話/イベント/脚本の必要枠。台詞本文、話者一覧、行数、シーン列は未定義。UI文言や汎用関係イベントを完成脚本に数えない。P26-02 / D08|
|
||||
|立ち絵|portraits.all とP22人物利用枠、160〜161・230・235・242行|主/サブ、表情、衣装、破損、汚れ、刻印、妊婦外見、ルート別描き分け。必要枚数・共有方針は未定義。P26-03 / D09|
|
||||
|音声|voice.all とP22人物voice枠、252・257行|ボイス収録・実装が欠損。話者/台詞/クリップ数/担当未定義。BGM、UI音、汎用音はボイスの代用にしない。P26-03 / D09|
|
||||
|図鑑|encyclopedia.all、9行|対象集合、ID、解放条件、項目数は未定義。D08|
|
||||
|横断する必要内容|clause.*、原本27〜34・132・134〜135・138〜161・229〜230・234〜235・242〜246・252・255〜258行|原本共通ルール、世界設定、CG/差分/モブ、SDモーション、制作段階を保存。今回新規の物語・台詞・場面は制作しない。P26-02/03、P22制作系とD08/D09|
|
||||
| 全章 | chapters.all、原本内容行、対応AR | 章名・章数・順序・シーン列は未定義。仕様書章番号を物語章数に転用しない |
|
||||
| 7名×3ルート | 原名の21 route行、人物別AR、P19セルの版固定と関係状態ソース | 共通状態・保存のみpartial。固有イベント列・自然到達・台詞・素材・終点はmissing、D08未決 |
|
||||
| 全エンド | endings.all / bad / game_overとAR | 全集合/本数は未定義。赤字終端の処理はpartialで物語エンド制作とは別 |
|
||||
| 台詞/立ち絵/音声/図鑑 | 各総括行、原文個別行、人物/用途AR、P22全614枠 | 完成本文・画像・ボイス・項目集合の完成証拠なし。汎用BGM/UI音は声の代用にしない |
|
||||
| 原本欠落の修復 | original.line.13 / 53 / 65 / 196 / 221 / 222、各原文全文・行・SHAとAR | イベント/台詞/CG/音声/専用操作内容はmissing。個別場面数はundefined |
|
||||
| 独立網羅 | 原本テキストの内容語彙・節見出しとARドメイン/condition/source_anchorsから選択 | 機械的な保守的選択方針を明記し、独立レビューが原単位網羅を判定 |
|
||||
| AR独立確認 | atomic_dispositionsで全ARに原レコード引用・所管原P単位・明示処置 | 旧view_results/statusを転記せず、未再制作/未定義を完成に昇格しない |
|
||||
| 所有と状態 | 引用ARのprimary_package/work_packagesと原P単位定義に結合 | 複数原P単位から特定制作担当を推測しない。owner_assignmentに担当未定義を明記 |
|
||||
| 負例 | 省略イベント行、AR欠落/未知/処置欠落、AR不一致owner、route/usage状態、stale AR/原文/usage SHA、partial誤完成 | 正式実行はRunner |
|
||||
|
||||
台帳の必要数・完成数はnull(未定義/未計数)。既知の7名×3ルートと、既存P22の614利用枠/用途群は、画像枚数、ボイス数、制作完了数、原子分母への換算をしない。未定義状態も欠損として明示する。状態語彙は produced / placeholder / code-drawing / partial / missing / undefined。完成を裏付ける証拠がないためproducedへの昇格はない。
|
||||
現在の生成物は762行(既存651行を保持し、選択した原文111行を追加)、関連AR675件。これは監査レコードの数であり、制作数量・完成数・受入分母ではない。全required_count/completed_countはnull。ARの関連選択外は追加施設契約/財布・採用等の別単位条件または独立メカニクスとして理由と原所有先を保持し、製品上の適用外/受入済みとはしない。
|
||||
|
||||
## 版と実証拠
|
||||
各行はatomic_requirement_idsを持つ。対応ARがない原文も消さず、所有先undefinedとして残す。ownerは引用ARのprimary_package、owner_deliverable_refsは固定参照の原P単位定義を指す。ARごとのowning_deliverablesにはprimary_packageとwork_packagesの各パッケージの元のid/title/work/acceptanceを保存する。特定の1単位が専任と原本で決まっていない場合、候補定義全体と担当未定義を示す。clause.*を一律P26-03に割り当てる旧処理を除いた。
|
||||
|
||||
計画定義の基準は `379a1ba8fd25b5ad24226178a2492f91761dc31d`、ゲーム監査アンカーは `f4f85bc54c39361f9efcd16f67a17835559ca70c`。TASK_SPEC raw SHA256は `8026040345de6b7d06cfce40beac9150bfe9ad303d6b26c11bd94e66b1080b29`、stripしたUTF-8表現は `1fedc9ade42a04722846daa9ac8ec26f821fb4efbce8dbbb53e3e76e31f38d02`。両表現を区別する。selectionは基準コミットの同一バイトで固定する。
|
||||
## 現物の部分支援と欠損
|
||||
|
||||
P19-03 REACHABILITY_TABLE.jsonは読取専用の既存監査でありP26の前身ではない。21セルの人物名・ルート名・natural_route_complete=falseを確認する。台帳にはそのセル参照とファイルSHAを保存する。実ソースのカタログ、OriginalRelationshipState、Runtime identity検査、relationship_serviceの保存コンテナ、PixelPeopleの汎用描画も直接参照し、正確な行範囲・本文・SHAを保持する。専用の明示状態検証/保存があることと、人物別ルートの内容制作・到達がないことを区別する。Runtimeの主人公ID `player` もこの証拠と全入力固定の対象。
|
||||
scripts/relationship/original_relationship_state.gd の明示値検証と relationship_service.gd の保存コンテナを直接参照する。route行と対応する原文行はpartialであり、content_status=missingを別記する。更新・攻撃・閾値・固有物語を実装済みとはしない。
|
||||
|
||||
P22-01は既存の360施設枠・176人物用途群・76その他用途群・2状態群を全て参照する。原レコードは型を保持して保存し、現在の表現先の実ファイルSHAを別に取得する。過去のSHAと現在のSHAが異なる場合も両方を残し、旧レコードを書き換えない。既存296mediaの全現物参照と、固定入力中の全assetsパス/SHAも記録する。汎用コード描画、仮素材、完成素材を混同しない。現物の存在だけで用途・差分の品質/権利受入済みとは判定しない。
|
||||
scripts/special/peeping_room_service.gd:127–162 と autoload/simulation_runtime.gd:10860–10880 を直接引用する。停止・対象参照・証拠キー/保存の基盤はあるが、製品配置はBUILD_DEFINITION_PENDING_D04、記録はRECORDER_ELIGIBILITY_PENDING_D04_D08_P16で止まる。覗き/記録/脅迫/カメラを引用する行のpartialはこの基盤だけを指し、視界、専用画面、スクリーンショット、介入や制作イベントを証明しない。
|
||||
|
||||
P22-01の元artifactsパスは非追跡アーカイブの別名であり、その入力はRootの公開コミット `137639773c9017f9ee7733d78e37179b687f965c` の `accepted-p22-01-inputs/` からも読める。公開manifest SHA256 `90b5a69d5f364012edabe5a68b8c7613f5f5ca47bc189d435dd2e69b8dd8c915` と全10ファイルのサイズ/SHAで固定する。元パスがある場合は公開バイトと一致必須、ない場合は固定git objectを使う。レビュー用別名の設置や新規ファイル追加は本workerでは行わない。P22-03 production_backlog.jsonはアンカー固定された未受入の下書き参考のみで、制作数量・承認・完了の権威にしない。
|
||||
scripts/guest/guest_service.gd:355–360 は夜間の欲求入力の切替のみ。L65のNTRイベント内容の制作証拠ではない。scripts/operations/operations_coordinator.gd:1183–1195 の赤字連続による終端計算は存在するが、完成シナリオ/画像/声ではない。L196の専用代物弁済イベントは現物制作証拠なしとしてmissing。数量/シーン列はundefinedのまま。
|
||||
|
||||
## チェッカーと正式試験
|
||||
P19/P22の旧状態をそのまま合否にしない。P19の正確な21セルと自然到達未完成の事実を版固定し、現在の関係ソースも独立参照する。P22全614用途レコードと全296mediaを保存し、表現先の現物SHAを監査アンカーへ再結合する。表現型からcode-drawing / placeholder / missingを区別し、歴史的inventoryのSHAと現在アンカーのSHAを別々に保持する。全assetsのパス/SHAも保存する。P22-03はdraft/unaccepted reference only。
|
||||
|
||||
`--check` は台帳・出典・固定入力・決定的Markdownを読取専用で検査する。`--source-binding-only` は同じ出典結合と台帳を確認し、Markdown表示の一致を要求しない。`--write` は固定原文・実ソースから指定JSON/Markdownの2ファイルだけを生成する。JSON重複キーと非有限数値も拒否する。
|
||||
P22の公開入力は137639773c9017f9ee7733d78e37179b687f965cのaccepted-p22-01-inputs/、manifest SHA256 90b5a69d5f364012edabe5a68b8c7613f5f5ca47bc189d435dd2e69b8dd8c915 に固定。元artifacts別名が存在すれば同一バイト必須、なければ公開git objectを読む。
|
||||
|
||||
元の30/88のacceptance/work/dependency/requirement等は基準版の計画定義へfail closedで結合し、明示した既存の進捗メタデータキーだけを除いて比較する。work_packages全体をバイト固定しない。atomic_requirementsとdecisionsは型/値を変えず基準版へ結合する。台帳は閉じたスキーマ相当の完全一致、ID一意性、原要求/利用枠集合、正確な人物/ルート、null数量、owner/status/type、引用とSHAを検査する。期待値は提出台帳から生成せず、固定原文の明示的な条項範囲・固定P19/P22・実ソースから作る。
|
||||
## 検査の構造と実行状況
|
||||
|
||||
負例は章/ルート/各エンド/台詞/立ち絵/音声/図鑑/共通カテゴリ/利用枠の欠落、架空数、欠損の完成昇格、owner/status/type/追加キー/重複ID、人物誤同定、原文/原文SHA/実証拠SHA/アンカー改変、不正JSON/重複キー/非有限値、Markdown drift、計画定義改変、公開manifest改変、選択入力の欠落/追加/変更/型改変を含む。正例は提出資料、独立期待集合、元の入力集合、許容される進捗メタデータ変更。
|
||||
check_content_ledger.py はstdlibのみ。--checkは読取専用の台帳/出典/入力/Markdown検査、--source-binding-onlyは出典結合と台帳検査、--writeは指定JSON/Markdown生成。JSON重複キー・非有限値は拒否する。
|
||||
|
||||
正式試験はRunnerが実行する。本workerは `--check`、`--source-binding-only`、負例テストを実行しておらず、合格は報告しない。JSON/Markdownは生成処理だけを実行した。正式コマンドは契約の p26-ledger / p26-negative / p26-source-binding の3本をそのまま使用する。
|
||||
authorities()は提出行や旧監査の合否を使わず、BASELINE本文の内容語彙/節見出し、ARのドメイン/condition/source_anchorsから必要行・ARを導く。選択条件はcoverage_policyに公開する。verify_authority()はcanonicalを引数に取らず、必要行とARの網羅、原文全文/行/hash、AR自身のrecord、所有原P単位、状態と表現pin、部分支援と内容欠損を検査する。canonical完全一致は追加のdrift検査として残す。原計画定義はベースへのprojectionで固定し、明示した進捗メタデータ以外の変更を拒否する。
|
||||
|
||||
全1368のselected game input、settings、assets、tests、tools、marker、policy、engine、roster、329parseとdefault player identityをチェッカーで固定入力マップへ照合する。固定pin SHA256は `6c778226a4cc6d0b0d682500759e7d4229c926a27bd2e247b00b9c3e5f1a2fb0`。追加/欠落/変更のいずれも拒否し、全選択入力をf4f85bcのgit blobにも照合する。docs helperはゲーム選択集合外。
|
||||
負例には従来の章/route/全エンド/category欠落、架空数、schema/type、重複ID、不正JSON、Markdown drift、原計画改変、公開manifest改変、ゲーム入力追加/欠落/改変を保持する。新しいindependent_rejectは正例も負例もverify_authorityだけを呼び、canonical一致に頼らずAR網羅・owner・状態・hash不正を捕捉する。各mutationに有効な提出資料のcontrolを置く。
|
||||
|
||||
既存nativeの184 suites / 21903 checks / 329 parse / 186 owned-endedは履歴としてのみ保存する。既存証拠のsource S `a7e3069b167511b3194242b3403fbcc35244a526` とC `65f07bf5eaa759a8e2e17c7bdbbbdbceb24ee373` への適用性は独立ゲートであり、本提出はnative再利用受入を主張しない。新規のnative full runは行わない。
|
||||
このworkerはJSON/Markdownの生成処理と読取による実装確認だけを行った。p26-ledger / p26-negative / p26-source-binding、--check、--source-binding-only、unittestは実行していない。PASSは主張しない。正式試験はRunnerが契約の3コマンドを実行する。
|
||||
|
||||
## 残存不足と受入境界
|
||||
## 新Sと受入の残ゲート
|
||||
|
||||
D05の既存未解決事項、D08の章/ルート/全エンド/図鑑/必要内容範囲、D09の品質/量/担当/予算/権利は未解決のまま。D05の既存承認を撤回も拡張もしない。P26-02/03と上流の制作・ゲームプレイ・保存/移行・素材受入は未完。held gestures、same-Windows、global acceptanceも未受入。本監査に製品操作は追加されない。
|
||||
FROZEN_GAME_INPUT_PINS.json SHA256 6c778226a4cc6d0b0d682500759e7d4229c926a27bd2e247b00b9c3e5f1a2fb0 と、全1368 selected game inputs・settings・assets・tests・tools・marker・policy・engine・roster・329parse・default player identityの照合処理は保持する。docs helpersは対象ゲーム集合外。TASK_SPEC raw SHAは8026040345de6b7d06cfce40beac9150bfe9ad303d6b26c11bd94e66b1080b29、strip表現は1fedc9ade42a04722846daa9ac8ec26f821fb4efbce8dbbb53e3e76e31f38d02。
|
||||
|
||||
whole-unit独立レビューが未定義の記録では原受入を満たさないと判断した場合は、正直に不合格/無加算とする。必要な通常prepare-integration、exact C/M、normal final、Rootの台帳更新/main pushはworker範囲外であり未実行。これらと独立受入が終わるまでは16/88へ進めない。
|
||||
旧SでのRoot source/input equality結果を新Sへ流用しない。workerはgit書込みを禁止されており、新Sの確定/Root qualificationはRunner・Rootの担当で未実行。新Sが存在した後に改めて全入力一致を資格確認する必要がある。
|
||||
|
||||
native 184 suites / 21903 checks / 329 parse / 186 owned-endedは履歴のみ。新規native/full runなし。exact S/Cの独立適用性確認前に再利用を承認しない。whole-unit独立Opusレビュー、prepare-integration、exact C/M、normal final、Rootの台帳/main pushも未実行。P26-02/03・上流制作、D08/D09、held gestures、same-Windows、global acceptanceは未完/未受入であり、これらの完了は主張しない。15/88から16/88への加算は行わない。
|
||||
|
||||
+205
-5
@@ -4,6 +4,7 @@ import argparse
|
||||
from functools import lru_cache
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -26,6 +27,53 @@ PEOPLE = [('anri', 'アンリ'), ('fine', 'フィーネ'), ('tomari', 'トマリ
|
||||
('amelia', 'アメリア'), ('liz', 'リズ'), ('mimi', 'ミミ'), ('satsuki', 'サツキ')]
|
||||
ROUTES = {'love': '純愛', 'ntr': 'NTR', 'corruption': '堕落'}
|
||||
STATUSES = {'produced', 'placeholder', 'code-drawing', 'partial', 'missing', 'undefined'}
|
||||
AR_PATH = PLAN + 'atomic_requirements.json'
|
||||
CONTENT_WORDS = re.compile(r'章|ストーリー|シナリオ|ルート|エンド|図鑑|台詞|会話|立ち絵|ボイス|音声|CG|スチル|モーション|アニメ|差分|イベント|覗|記録係|脅迫|介入|カメラ|アングル|スクリーンショット|代物弁済|破産|履歴|妊娠|刻印|衣装|キャラクター|ヒロイン')
|
||||
CONTENT_DOMAINS = ('AR.heroine.', 'AR.asset.', 'AR.art.', 'AR.narrative.',
|
||||
'AR.observation.', 'AR.route_action.', 'AR.progression.')
|
||||
APPROVAL_COMMIT = 'a13f7c0b791dd385ff8113c3d321c0e3fe7d7c94'
|
||||
APPROVAL_PATH = PLAN + 'execution/p04-01-owner-approval-20261018/D05_ACTUAL_HUMAN_APPROVAL.json'
|
||||
APPROVAL_HASH = 'cd365ded1b777a0327d090709f0a4161f5005562819183455d9f2e106fba3e96'
|
||||
|
||||
|
||||
def authorities(root):
|
||||
"""Selection reads authority text, never generated rows or old audit statuses.
|
||||
|
||||
All non-heading content-bearing lines plus whole narrative/character/art
|
||||
sections are conservative audit scope. Every AR receives a disposition;
|
||||
unrelated mechanics are explicitly outside this documentation unit.
|
||||
"""
|
||||
require(sha((root / BASELINE).read_bytes()) == BASE_HASH, 'authority BASELINE hash')
|
||||
require((root / AR_PATH).read_bytes() == blob(root, BASE, AR_PATH), 'authority AR hash')
|
||||
lines = (root / BASELINE).read_text().splitlines()
|
||||
required = set()
|
||||
section_content = False
|
||||
for n, text in enumerate(lines, 1):
|
||||
if text.startswith('# '):
|
||||
section_content = bool(re.search(r'世界観|ユニークキャラクター|恋愛|デザイン|アセット|ロードマップ', text))
|
||||
if text.strip() and not text.startswith('#') and (section_content or CONTENT_WORDS.search(text)):
|
||||
required.add(n)
|
||||
records = decode((root / AR_PATH).read_bytes())['requirements']
|
||||
relevant = {a['id'] for a in records if atomic_lines(a) and (a['id'].startswith(CONTENT_DOMAINS)
|
||||
or CONTENT_WORDS.search(a['condition']) or any(
|
||||
s['path'] == BASELINE and required.intersection(s['lines'])
|
||||
for s in a['source_anchors']))}
|
||||
required.update(n for a in records if a['id'] in relevant for n in atomic_lines(a))
|
||||
packages = decode(blob(root, BASE, PLAN + 'work_packages.json'))
|
||||
return lines, required, {a['id']: a for a in records}, relevant, {p['id']: p for p in packages}
|
||||
|
||||
|
||||
def atomic_lines(record):
|
||||
return {n for s in record['source_anchors'] if s['path'] == BASELINE for n in s['lines']}
|
||||
|
||||
|
||||
def owning_units(record, packages):
|
||||
# Preserve the actual definitions; do not invent a uniquely assigned unit
|
||||
# where the original package has several remaining deliverables.
|
||||
return [{'package': p, 'primary': p == record['primary_package'],
|
||||
'deliverables': [{k: u[k] for k in ('id', 'title', 'work', 'acceptance')}
|
||||
for u in packages[p]['remaining_deliverables']]}
|
||||
for p in dict.fromkeys([record['primary_package']] + record['work_packages'])]
|
||||
|
||||
|
||||
def require(ok, message):
|
||||
@@ -255,8 +303,94 @@ def expected(root):
|
||||
current_media = [dict(source(root, m['path']), historical_inventory=m) for m in media]
|
||||
pins = decode((root / OUT / 'FROZEN_GAME_INPUT_PINS.json').read_bytes())
|
||||
available_assets = {p: v for p, v in pins['source_files'].items() if p.startswith('assets/')}
|
||||
original_lines, required_lines, atoms, relevant, packages = authorities(root)
|
||||
# A separate row for every selected original clause prevents a broad category
|
||||
# citation from concealing an omitted event/control requirement.
|
||||
for n in sorted(required_lines):
|
||||
add('original.line.' + str(n), 'original-clause', [(n,)],
|
||||
'原本の必要条件。個別の脚本/台詞/CG/音声/場面数は定義しない。',
|
||||
'undefined', 'missing', reason='原本条件を保存。専用制作内容は欠損、場面ID・数量は未定義。下記AR所有先で残作業を追跡。')
|
||||
relationship_paths = ['scripts/relationship/original_relationship_state.gd',
|
||||
'scripts/relationship/relationship_service.gd']
|
||||
relation_pins = [source(root, p) for p in relationship_paths]
|
||||
# Direct source inspection is limited to explicit state/container support.
|
||||
# These excerpts never assert authored scenes, usable observation controls,
|
||||
# or a naturally reachable character route.
|
||||
require('class_name OriginalRelationshipState' in read_source(root, relationship_paths[0]).decode(),
|
||||
'relationship class evidence')
|
||||
peep_pin = excerpt(root, 'scripts/special/peeping_room_service.gd', 127, 162)
|
||||
peep_runtime_pin = excerpt(root, 'autoload/simulation_runtime.gd', 10860, 10880)
|
||||
require('RECORDER_ELIGIBILITY_PENDING_D04_D08_P16' in peep_pin['text'] and
|
||||
'BUILD_DEFINITION_PENDING_D04' in peep_runtime_pin['text'], 'peeping backend boundary')
|
||||
night_pin = excerpt(root, 'scripts/guest/guest_service.gd', 355, 360)
|
||||
terminal_pin = excerpt(root, 'scripts/operations/operations_coordinator.gd', 1183, 1195)
|
||||
dispositions = {}
|
||||
for row in rows:
|
||||
cited = {n for c in row['citations'] for n in range(c['lines'][0], c['lines'][1] + 1)}
|
||||
matches = sorted(a for a in relevant if cited.intersection(atomic_lines(atoms[a])))
|
||||
if row['kind'] == 'route':
|
||||
matches = ['AR.heroine.' + row['identity']['character_id'] + '.route.' + row['identity']['route_id']]
|
||||
row['atomic_requirement_ids'] = matches
|
||||
owners = sorted({atoms[a]['primary_package'] for a in matches})
|
||||
row['owner'] = '/'.join(owners) if owners else 'undefined'
|
||||
row['owner_deliverable_refs'] = owners
|
||||
row['owner_assignment'] = 'original package deliverables; specific production assignee undefined'
|
||||
row['content_status'] = row['status']
|
||||
row['support_scope'] = 'no dedicated support established'
|
||||
# Shared relationship model is genuine partial support, while content
|
||||
# and unknown catalogs remain missing/undefined separately.
|
||||
if row['kind'] == 'route' or row['id'] == 'clause.route_rules' or (
|
||||
row['id'].startswith('original.line.') and any(
|
||||
a.startswith(('AR.relationship.', 'AR.route_action.', 'AR.heroine.')) for a in matches)):
|
||||
row['evidence'] += relation_pins
|
||||
row['status'] = 'partial'
|
||||
row['support_scope'] = 'shared relationship state/container only; no character-specific production or natural completion'
|
||||
row['reason'] += ' 共通関係状態・保存の部分支援のみ。専用イベント/台詞/CG/ボイスは欠損。'
|
||||
if row['id'].startswith('clause.'):
|
||||
row['reason'] += ' 所有先は引用ARのprimary_packageと原P単位定義。包括P26-03への割当はしない。'
|
||||
if row['kind'] != 'route' and not row['id'].startswith('usage.'):
|
||||
text = '\n'.join(c['text'] for c in row['citations'])
|
||||
support, descriptions = [], []
|
||||
if re.search(r'覗|マジックミラー|記録係|カメラ|スクリーンショット|脅迫', text):
|
||||
support += [peep_pin, peep_runtime_pin]
|
||||
descriptions.append('stopped room/reference/evidence container only; production placement/recorder eligibility pending; no view/camera/intervention/content')
|
||||
if 'NTRイベント発生' in text:
|
||||
support.append(night_pin)
|
||||
descriptions.append('night-dependent guest need input only; no night NTR event production')
|
||||
if 'ゲームオーバー' in text:
|
||||
support.append(terminal_pin)
|
||||
descriptions.append('bankruptcy terminal bookkeeping only; no authored ending')
|
||||
if support:
|
||||
row['status'] = 'partial'
|
||||
row['evidence'] += support
|
||||
row['support_scope'] += '; ' + '; '.join(descriptions)
|
||||
row['reason'] += ' 引用ソースの部分機構のみ。内容状態と数量未定義を別記し、完成とは判定しない。'
|
||||
for identity, atom in atoms.items():
|
||||
linked = [r['id'] for r in rows if identity in r['atomic_requirement_ids']]
|
||||
dispositions[identity] = {
|
||||
'record': {k: atom[k] for k in ('id', 'condition', 'source_anchors',
|
||||
'source_sha256', 'primary_package', 'work_packages')},
|
||||
'rows': linked,
|
||||
'disposition': 'covered-by-row' if identity in relevant else 'out-of-scope-with-reason',
|
||||
'reason': ('原本条件・欠損は各行に記録。ARの旧監査合否は転記しない。'
|
||||
if identity in relevant else '原本内容行に結合しない追加施設契約/採用・財布条件、または内容語彙/対象節/内容ARドメインに該当しない独立メカニクス。製品受入は当該原P単位の責任。'),
|
||||
'owning_deliverables': owning_units(atom, packages)}
|
||||
approval_raw = blob(root, APPROVAL_COMMIT, APPROVAL_PATH)
|
||||
require(sha(approval_raw) == APPROVAL_HASH, 'published D05 approval')
|
||||
approval = {'commit': APPROVAL_COMMIT, 'path': APPROVAL_PATH,
|
||||
'sha256': APPROVAL_HASH, 'record': decode(approval_raw),
|
||||
'scope': 'actual approval received; P04 product adoption remains outside P26'}
|
||||
return {'task_id': 'P26-01-LEDGER', 'schema_version': 1, 'source_anchor': ANCHOR,
|
||||
'definition_base': BASE, 'rows': rows, 'usage_snapshots': usage_snapshots,
|
||||
'coverage_policy': {'line_selector': CONTENT_WORDS.pattern,
|
||||
'whole_sections': '世界観|ユニークキャラクター|恋愛|デザイン|アセット|ロードマップ',
|
||||
'ar_domains': list(CONTENT_DOMAINS), 'selected_original_lines': sorted(required_lines),
|
||||
'relevant_ar_ids': sorted(relevant)},
|
||||
'atomic_authority': source(root, AR_PATH), 'atomic_dispositions': dispositions,
|
||||
'owning_package_definitions': {p: {'title': v['title'],
|
||||
'remaining_deliverables': [{k: u[k] for k in ('id', 'title', 'work', 'acceptance')}
|
||||
for u in v['remaining_deliverables']]} for p, v in packages.items()},
|
||||
'current_D05_approval': approval,
|
||||
'published_material': {'anchor': PUBLISHED, 'manifest_sha256': MANIFEST_HASH,
|
||||
'manifest_path': ARCHIVE + 'ROOT_MATERIAL_PRESERVATION_MANIFEST.json'},
|
||||
'references': references, 'P22_03_authority': 'draft/unaccepted reference only',
|
||||
@@ -264,7 +398,7 @@ def expected(root):
|
||||
'support_interpretation': 'Explicit state/identity/container support only; generic pose has no original character-specific appearance. No finished chapter/dialogue/portrait/voice/encyclopedia inferred.',
|
||||
'current_media_reaudit': current_media,
|
||||
'available_asset_source_set': available_assets,
|
||||
'pending': {'D05': 'unresolved existing questions; existing approvals preserved',
|
||||
'pending': {'D05': 'Historical register only: unresolved as of audit anchor f4f85bc54c39361f9efcd16f67a17835559ca70c and base 379a1ba8fd25b5ad24226178a2492f91761dc31d. Current actual approval is separately pinned; P04 adoption pending outside P26.',
|
||||
'D08': 'unresolved', 'D09': 'unresolved'},
|
||||
'accounting': {'packages': 30, 'units': 88, 'completed': 15, 'credit': 0,
|
||||
'partial_credit': False, 'whole_unit_accepted': False},
|
||||
@@ -273,7 +407,69 @@ def expected(root):
|
||||
'reason': 'Runner must independently establish exact S/C applicability; no native result claimed here.'}}
|
||||
|
||||
|
||||
def verify(candidate, canonical):
|
||||
def verify_authority(candidate, root):
|
||||
"""Non-canonical coverage/ownership/citation checks, callable on their own."""
|
||||
lines, required, atoms, relevant, packages = authorities(root)
|
||||
seen_lines, seen_atoms = set(), set()
|
||||
row_ids = {r['id'] for r in candidate['rows']}
|
||||
for row in candidate['rows']:
|
||||
require(set(row) == {'id', 'kind', 'identity', 'citations', 'required_condition',
|
||||
'status', 'required_count', 'completed_count', 'evidence', 'missing', 'reason',
|
||||
'owner', 'atomic_requirement_ids', 'owner_deliverable_refs', 'owner_assignment',
|
||||
'content_status', 'support_scope'}, 'closed row schema')
|
||||
require(row['status'] in STATUSES, 'invalid row status')
|
||||
require(row['required_count'] is None and row['completed_count'] is None, 'invented quantity')
|
||||
require(type(row['atomic_requirement_ids']) is list, 'AR ID list')
|
||||
ids = row['atomic_requirement_ids']
|
||||
require(set(ids) <= relevant, 'unknown or unrelated AR ID')
|
||||
owners = sorted({atoms[a]['primary_package'] for a in ids})
|
||||
require(row['owner'] == ('/'.join(owners) if owners else 'undefined') and
|
||||
row['owner_deliverable_refs'] == owners, 'AR owner mismatch')
|
||||
cited = set()
|
||||
for c in row['citations']:
|
||||
lo, hi = c['lines']
|
||||
require(type(lo) is int and type(hi) is int and 1 <= lo <= hi <= len(lines), 'citation lines')
|
||||
require(c['path'] == BASELINE and c['sha256'] == BASE_HASH and
|
||||
c['text'] == '\n'.join(lines[lo-1:hi]), 'original citation integrity')
|
||||
cited.update(range(lo, hi + 1))
|
||||
require(all(cited.intersection(atomic_lines(atoms[a])) for a in ids), 'AR citation correspondence')
|
||||
seen_lines.update(cited)
|
||||
seen_atoms.update(ids)
|
||||
require(row['content_status'] in STATUSES and row['content_status'] != 'produced', 'unsupported content production')
|
||||
require(row['status'] != 'produced', 'partial support falsely produced')
|
||||
if row['status'] == 'partial':
|
||||
require(row['evidence'] and 'only' in row['support_scope'], 'unqualified partial support')
|
||||
for pin in row['evidence']:
|
||||
require(encoded({k: pin[k] for k in ('path', 'sha256', 'bytes', 'anchor', 'anchor_path')}) ==
|
||||
encoded(source(root, pin['path'])), 'row evidence integrity')
|
||||
if row['id'].startswith('usage.'):
|
||||
reps = row['evidence']
|
||||
status = ('code-drawing' if all(p['historical_inventory']['representation'] == 'code_drawn'
|
||||
for p in reps) else 'placeholder') if reps else 'missing'
|
||||
require(row['status'] == status and row['content_status'] == status, 'usage representation status')
|
||||
if row['kind'] == 'route':
|
||||
require(row['status'] == 'partial' and row['content_status'] == 'missing', 'route support/content distinction')
|
||||
identity = row['identity']
|
||||
require((identity['character_id'], identity['original_name']) in PEOPLE and
|
||||
ROUTES.get(identity['route_id']) == identity['route_name'], 'original route identity')
|
||||
require(ids == ['AR.heroine.' + identity['character_id'] + '.route.' + identity['route_id']], 'route AR identity')
|
||||
require(required <= seen_lines, 'independent original line coverage')
|
||||
require(relevant <= seen_atoms, 'independent AR coverage')
|
||||
require(set(candidate['atomic_dispositions']) == set(atoms), 'AR disposition set')
|
||||
for identity, atom in atoms.items():
|
||||
entry = candidate['atomic_dispositions'][identity]
|
||||
require(entry['record'] == {k: atom[k] for k in entry['record']} and
|
||||
set(entry['record']) == {'id', 'condition', 'source_anchors', 'source_sha256', 'primary_package', 'work_packages'},
|
||||
'stale AR record')
|
||||
require(entry['owning_deliverables'] == owning_units(atom, packages), 'AR owning deliverables')
|
||||
require(entry['reason'] and entry.get('disposition') == (
|
||||
'covered-by-row' if identity in relevant else 'out-of-scope-with-reason'), 'missing AR disposition')
|
||||
require(set(entry['rows']) <= row_ids and set(entry['rows']) == {
|
||||
r['id'] for r in candidate['rows'] if identity in r['atomic_requirement_ids']}, 'AR row linkage')
|
||||
require(identity not in relevant or entry['rows'], 'AR without content row')
|
||||
|
||||
|
||||
def verify(candidate, canonical, root=None):
|
||||
require(type(candidate) is dict, 'closed document object')
|
||||
require(type(candidate.get('rows')) is list, 'closed rows list')
|
||||
rows = candidate['rows']
|
||||
@@ -284,8 +480,9 @@ def verify(candidate, canonical):
|
||||
require(row['status'] in STATUSES, 'invalid status')
|
||||
require(row['required_count'] is None and row['completed_count'] is None, 'invented quantity')
|
||||
require(row['status'] != 'produced', 'unsupported completion')
|
||||
require(row['owner'] in {'D08', 'P26-02', 'P26-03'}, 'invalid owner')
|
||||
require(row['citations'] and row['reason'] and row['missing'], 'missing traceability')
|
||||
require(root is not None, 'independent authority root required')
|
||||
verify_authority(candidate, root)
|
||||
require(encoded(candidate) == encoded(canonical), 'ledger differs from original/source-bound audit')
|
||||
|
||||
|
||||
@@ -293,11 +490,14 @@ def markdown(document):
|
||||
# A lossless deterministic human view, including exact citations and source hashes.
|
||||
lines = ['# P26-01 全必要内容の台帳', '',
|
||||
'15/88、今回加算0。未制作/未定義は欠損。利用枠数は完成数ではない。', '',
|
||||
'D05既存未解決事項・D08・D09は未解決。P22-03は未受入の参考資料。', '']
|
||||
'D05の過去未解決記録はf4f85bc/379a1ba時点。現行承認は別途固定。D08/D09未解決。P22-03は未受入参考。', '']
|
||||
for row in document['rows']:
|
||||
lines += ['## ' + row['id'], '', '- 種別: ' + row['kind'],
|
||||
'- 識別: ' + encoded(row['identity']),
|
||||
'- 状態: ' + row['status'], '- 所管: ' + row['owner'],
|
||||
'- 内容状態: ' + row['content_status'], '- 部分支援範囲: ' + row['support_scope'],
|
||||
'- AR: ' + encoded(row['atomic_requirement_ids']),
|
||||
'- 所管単位: ' + encoded(row['owner_deliverable_refs']) + ' (固定参照の原P単位定義を参照)',
|
||||
'- 条件: ' + (row['required_condition'] if isinstance(row['required_condition'], str)
|
||||
else encoded(row['required_condition'])), '- 理由: ' + row['reason'],
|
||||
'- 必要数/完成数: 未定義 / 未計数', '- 欠損: ' + row['missing'], '']
|
||||
@@ -332,7 +532,7 @@ def main():
|
||||
(root / OUT / 'CONTENT_LEDGER.md').write_text(markdown(canonical))
|
||||
else:
|
||||
document = decode((root / OUT / 'CONTENT_LEDGER.json').read_bytes())
|
||||
verify(document, canonical)
|
||||
verify(document, canonical, root)
|
||||
if args.check:
|
||||
check_view((root / OUT / 'CONTENT_LEDGER.md').read_bytes(), document)
|
||||
print(json.dumps({'documentation_check': 'pass', **result}, ensure_ascii=False))
|
||||
|
||||
@@ -15,14 +15,15 @@ class LedgerTests(unittest.TestCase):
|
||||
cls.submitted = audit.decode((ROOT / audit.OUT / 'CONTENT_LEDGER.json').read_bytes())
|
||||
|
||||
def reject(self, mutate):
|
||||
audit.verify(self.submitted, self.canonical, ROOT)
|
||||
value = copy.deepcopy(self.submitted)
|
||||
mutate(value)
|
||||
with self.assertRaises((ValueError, TypeError, KeyError)):
|
||||
audit.verify(value, self.canonical)
|
||||
audit.verify(value, self.canonical, ROOT)
|
||||
|
||||
def test_valid_submitted_and_independent_source_control(self):
|
||||
audit.verify(self.submitted, self.canonical)
|
||||
audit.verify(copy.deepcopy(self.canonical), self.canonical)
|
||||
audit.verify(self.submitted, self.canonical, ROOT)
|
||||
audit.verify(copy.deepcopy(self.canonical), self.canonical, ROOT)
|
||||
audit.check_view((ROOT / audit.OUT / 'CONTENT_LEDGER.md').read_bytes(), self.submitted)
|
||||
|
||||
def test_required_sets_are_independent_of_submitted_rows(self):
|
||||
@@ -40,7 +41,8 @@ class LedgerTests(unittest.TestCase):
|
||||
self.reject(lambda d: d.update(rows=[r for r in d['rows'] if r['id'] != identity]))
|
||||
|
||||
def test_missing_usage_slot(self):
|
||||
self.reject(lambda d: d['rows'].pop())
|
||||
self.reject(lambda d: d.update(rows=[r for r in d['rows'] if r['id'] !=
|
||||
next(x['id'] for x in d['rows'] if x['id'].startswith('usage.'))]))
|
||||
|
||||
def test_fabricated_quantity_and_false_completion(self):
|
||||
self.reject(lambda d: d['rows'][0].update(required_count=8))
|
||||
@@ -115,6 +117,62 @@ class LedgerTests(unittest.TestCase):
|
||||
with self.subTest(mode=mode), patch.object(audit, 'selection', return_value=changed), self.assertRaises(ValueError):
|
||||
audit.binding(ROOT)
|
||||
|
||||
def independent_reject(self, mutate, message):
|
||||
# The positive control and rejection invoke only the independent layer:
|
||||
# no canonical document/equality can make these tests pass.
|
||||
audit.verify_authority(self.submitted, ROOT)
|
||||
value = copy.deepcopy(self.submitted)
|
||||
mutate(value)
|
||||
with self.assertRaisesRegex(ValueError, message):
|
||||
audit.verify_authority(value, ROOT)
|
||||
|
||||
def test_omitted_review_event_clauses(self):
|
||||
for n in (13, 53, 65, 196, 221, 222):
|
||||
with self.subTest(line=n):
|
||||
self.reject(lambda d: d.update(rows=[r for r in d['rows']
|
||||
if r['id'] != 'original.line.' + str(n)]))
|
||||
# Remove a line from every citation (including broad category ranges),
|
||||
# preserving other lines, AR links and IDs: authority coverage must fail.
|
||||
def omit(d):
|
||||
for row in d['rows']:
|
||||
row['citations'] = [c for c in row['citations']
|
||||
if not c['lines'][0] <= 65 <= c['lines'][1]]
|
||||
self.independent_reject(omit, 'AR citation correspondence|independent original line coverage')
|
||||
|
||||
def test_atomic_coverage_unknown_and_missing_disposition(self):
|
||||
identity = 'AR.events.ntr_night_peak'
|
||||
def remove(d):
|
||||
for row in d['rows']:
|
||||
if identity in row['atomic_requirement_ids']:
|
||||
row['atomic_requirement_ids'].remove(identity)
|
||||
atoms = audit.authorities(ROOT)[2]
|
||||
owners = sorted({atoms[a]['primary_package'] for a in row['atomic_requirement_ids']})
|
||||
row.update(owner='/'.join(owners) if owners else 'undefined', owner_deliverable_refs=owners)
|
||||
self.independent_reject(remove, 'independent AR coverage')
|
||||
self.independent_reject(lambda d: d['rows'][0]['atomic_requirement_ids'].append('AR.unknown'), 'unknown or unrelated AR ID')
|
||||
self.independent_reject(lambda d: d['atomic_dispositions'][identity].pop('disposition'), 'disposition')
|
||||
|
||||
def test_owners_route_usage_and_partial_truth(self):
|
||||
for prefix in ('route.', 'usage.', 'original.line.65'):
|
||||
with self.subTest(prefix=prefix):
|
||||
self.independent_reject(lambda d: next(r for r in d['rows']
|
||||
if r['id'].startswith(prefix)).update(owner='P26-03'), 'AR owner mismatch')
|
||||
self.independent_reject(lambda d: next(r for r in d['rows'] if r['kind']=='route')
|
||||
.update(status='missing'), 'route support/content distinction')
|
||||
self.independent_reject(lambda d: next(r for r in d['rows'] if r['id'].startswith('usage.') and r['evidence'])
|
||||
.update(status='missing'), 'usage representation status')
|
||||
self.independent_reject(lambda d: next(r for r in d['rows'] if r['status']=='partial')
|
||||
.update(status='produced'), 'partial support falsely produced')
|
||||
|
||||
def test_stale_atomic_original_and_usage_pins(self):
|
||||
identity = 'AR.events.ntr_night_peak'
|
||||
self.independent_reject(lambda d: d['atomic_dispositions'][identity]['record']
|
||||
.update(source_sha256='0'*64), 'stale AR record')
|
||||
self.independent_reject(lambda d: d['rows'][0]['citations'][0]
|
||||
.update(sha256='0'*64), 'original citation integrity')
|
||||
self.independent_reject(lambda d: next(r for r in d['rows'] if r['id'].startswith('usage.') and r['evidence'])
|
||||
['evidence'][0].update(sha256='0'*64), 'row evidence integrity')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
Reference in New Issue
Block a user