P26-01-LEDGER: 原P26-01 全必要内容の台帳
This commit is contained in:
+137355
-1581
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
+17
-3
@@ -12,13 +12,25 @@ build/owner-handoff/p26-repair3/ の ACTUAL_WHOLE_REVIEW.json、ACTUAL_OPUS_REPA
|
||||
|
||||
現在の実承認は 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は未解決。
|
||||
|
||||
## whole-unit対応と4修復
|
||||
## 修復5: 証拠のAR別帰属
|
||||
|
||||
今回の変更前に build/owner-handoff/p26-support-attribution-repair/ACTUAL_OPUS_ASTRA_ATTRIBUTION_DIRECTION.json と ACTUAL_WHOLE_REVIEW_DA80360A.json を全文読了し、D05の上記4資料も再確認した。修復5は証拠帰属に限定する。
|
||||
|
||||
結合した引用文のキーワードからpartialを付ける処理を削除した。SUPPORT_POLICYは証拠種別→許容ARの閉じた対応表である。関係状態は21ルートと感情3軸/盾最大値の明示状態・保存だけ、覗きは記録係/観察/証拠収集に関する列挙ARの停止中コンテナだけ、夜間は2 ARの欲求入力だけ、赤字は終端計算ARだけを支援する。人物のidentity/技能や任意のrelationship/route_action全域へは拡張しない。視点・角度・介入そのものを支援する証拠とはしない。
|
||||
|
||||
各行のscoped_supportは対象AR・証拠種別・限定範囲・content_status=missingを明記する。unsupported_atomic_requirement_idsはその行で基盤支援がない残りのARを列挙する(用途行の既存placeholder/code-drawingは従来どおり別記)。L9は21ルートのコンテナだけpartialで、全エンド/図鑑/所要時間は未支援。L257とclause.productionは観察工程のコンテナだけpartialで、ボイス収録/実装を含む他ARは未支援。voice.all、endings全3行、encyclopedia.allはmissing、証拠なし。声に関するどのARも対応表に入れない。行数・AR対応・引用・制作数量は増減させない。
|
||||
|
||||
verify_authorityはexpectedやcanonicalを呼ばず、提出された各claimのARと行の種類、固定出典・抜粋全文、支援/未支援ARの分割、状態と範囲文を検査する。有効なSHAでも無関係な証拠、無根拠なpartial、声への同一引用行の観察証拠の流用は拒否する。負例はmissingの音声/立ち絵/エンド/図鑑への証拠注入、混合行内の支援AR拡張、正しい夜間支援への無関係な証拠追加を含む。route、L65、L53の実際のpartialを正例にする。既存負例は維持し、AR削除負例だけ新しい分割フィールドも整合して削除する。
|
||||
|
||||
矛盾した「no dedicated support established; …only」の連結を廃止した。任意LOWのL226はAR.ui.cut_duel/cut_defenseの対象外理由にP24 UIカットインを明記した。owner決定用content ARの追加分類は、既存の原典リンクと所有先を狭める別の判断が必要なため今回はdeferred。ownerは引き続き引用ARのprimary_packageに基づく追跡先で、専任制作担当の確定ではない。
|
||||
|
||||
## whole-unit対応と残存不足
|
||||
|
||||
| 対象 | 実際の記録・修復 | 残存不足 |
|
||||
| --- | --- | --- |
|
||||
| 全章 | chapters.all、原本内容行、対応AR | 章名・章数・順序・シーン列は未定義。仕様書章番号を物語章数に転用しない |
|
||||
| 7名×3ルート | 原名の21 route行、人物別AR、P19セルの版固定と関係状態ソース | 共通状態・保存のみpartial。固有イベント列・自然到達・台詞・素材・終点はmissing、D08未決 |
|
||||
| 全エンド | endings.all / bad / game_overとAR | 全集合/本数は未定義。赤字終端の処理はpartialで物語エンド制作とは別 |
|
||||
| 全エンド | endings.all / bad / game_overとAR | 全3行missing、全集合/本数は未定義。赤字終端の処理はoriginal.line.72の該当ARだけpartial |
|
||||
| 台詞/立ち絵/音声/図鑑 | 各総括行、原文個別行、人物/用途AR、P22全614枠 | 完成本文・画像・ボイス・項目集合の完成証拠なし。汎用BGM/UI音は声の代用にしない |
|
||||
| 原本欠落の修復 | original.line.13 / 53 / 65 / 196 / 221 / 222、各原文全文・行・SHAとAR | イベント/台詞/CG/音声/専用操作内容はmissing。個別場面数はundefined |
|
||||
| 独立網羅 | 原本テキストの内容語彙・節見出しとARドメイン/condition/source_anchorsから選択 | 機械的な保守的選択方針を明記し、独立レビューが原単位網羅を判定 |
|
||||
@@ -34,7 +46,7 @@ build/owner-handoff/p26-repair3/ の ACTUAL_WHOLE_REVIEW.json、ACTUAL_OPUS_REPA
|
||||
|
||||
scripts/relationship/original_relationship_state.gd の明示値検証と relationship_service.gd の保存コンテナを直接参照する。route行と対応する原文行はpartialであり、content_status=missingを別記する。更新・攻撃・閾値・固有物語を実装済みとはしない。
|
||||
|
||||
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はこの基盤だけを指し、視界、専用画面、スクリーンショット、介入や制作イベントを証明しない。
|
||||
scripts/special/peeping_room_service.gd:127–162 と autoload/simulation_runtime.gd:10860–10880 を直接引用する。停止・対象参照・証拠キーの基盤はあるが、製品配置はBUILD_DEFINITION_PENDING_D04、記録はRECORDER_ELIGIBILITY_PENDING_D04_D08_P16で止まる。閉じた対応表にあるARのscoped_supportだけがこの基盤を指し、視界、専用画面、スクリーンショット、介入や制作イベントを証明しない。L13の自律観察/介入、L53の専用視点、L222の角度/介入にはこの証拠を帰属させない。
|
||||
|
||||
scripts/guest/guest_service.gd:355–360 は夜間の欲求入力の切替のみ。L65のNTRイベント内容の制作証拠ではない。scripts/operations/operations_coordinator.gd:1183–1195 の赤字連続による終端計算は存在するが、完成シナリオ/画像/声ではない。L196の専用代物弁済イベントは現物制作証拠なしとしてmissing。数量/シーン列はundefinedのまま。
|
||||
|
||||
@@ -52,6 +64,8 @@ authorities()は提出行や旧監査の合否を使わず、BASELINE本文の
|
||||
|
||||
このworkerはJSON/Markdownの生成処理と読取による実装確認だけを行った。p26-ledger / p26-negative / p26-source-binding、--check、--source-binding-only、unittestは実行していない。PASSは主張しない。正式試験はRunnerが契約の3コマンドを実行する。
|
||||
|
||||
修復5の生成後の読取比較では762行、1309 AR処置、675関連ARを保持し、全行のID・引用・ARリンクは開始時と同一。全required_count/completed_countはnull。JSONの変更した最上位領域はrowsとatomic_dispositionsのみで、D05の4回答を含む他の領域は同一。JSONとMDはexpected/markdownによる決定的生成であり、試験実行結果ではない。生成後に変更したcheckerの独立検証と負例はRunnerでの実行待ち。
|
||||
|
||||
## 新Sと受入の残ゲート
|
||||
|
||||
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。
|
||||
|
||||
+107
-36
@@ -35,6 +35,45 @@ APPROVAL_COMMIT = 'a13f7c0b791dd385ff8113c3d321c0e3fe7d7c94'
|
||||
APPROVAL_PATH = PLAN + 'execution/p04-01-owner-approval-20261018/D05_ACTUAL_HUMAN_APPROVAL.json'
|
||||
APPROVAL_HASH = 'cd365ded1b777a0327d090709f0a4161f5005562819183455d9f2e106fba3e96'
|
||||
|
||||
# Closed attribution policy: membership means only the named backend fragment,
|
||||
# never the whole AR, its content, or other ARs sharing its source line.
|
||||
SUPPORT_POLICY = {
|
||||
'relationship-container': {
|
||||
'ars': frozenset({'AR.relationship.three_emotions',
|
||||
'AR.relationship.affection_shield_max'} |
|
||||
{'AR.heroine.' + p + '.route.' + r for p, _ in PEOPLE for r in ROUTES}),
|
||||
'sources': [('scripts/relationship/original_relationship_state.gd', None),
|
||||
('scripts/relationship/relationship_service.gd', None)],
|
||||
'scope': 'shared explicit relationship state/validation/save container only; no updates, attacks, thresholds, authored content or natural route completion'},
|
||||
'peeping-container': {
|
||||
'ars': frozenset({'AR.observation.recorder', 'AR.underground.observation',
|
||||
'AR.underground.blackmail', 'AR.route_action.observation_progress',
|
||||
'AR.ui.observation_camera_entry', 'AR.ui.observation_capture',
|
||||
'AR.schedule.B-14.3.observation'}),
|
||||
'sources': [('scripts/special/peeping_room_service.gd', (127, 162)),
|
||||
('autoload/simulation_runtime.gd', (10860, 10880))],
|
||||
'scope': 'stopped room/target reference and fixture evidence-key container only; production placement/recorder eligibility pending; no view, camera UI, screenshot, intervention, transaction or authored content'},
|
||||
'night-input': {
|
||||
'ars': frozenset({'AR.events.ntr_night_peak', 'AR.guest.need_sexual_night_peak'}),
|
||||
'sources': [('scripts/guest/guest_service.gd', (355, 360))],
|
||||
'scope': 'night-dependent guest need input only; no event production or peak-frequency implementation'},
|
||||
'bankruptcy-bookkeeping': {
|
||||
'ars': frozenset({'AR.progression.three_deficits_gameover'}),
|
||||
'sources': [('scripts/operations/operations_coordinator.gd', (1183, 1195))],
|
||||
'scope': 'bankruptcy terminal bookkeeping only; no authored ending'},
|
||||
}
|
||||
|
||||
|
||||
def support_family(row):
|
||||
# Content summaries cannot inherit support from neighbouring schedule ARs.
|
||||
return (row['kind'] == 'route' or row['id'].startswith('original.line.') or
|
||||
row['id'] in {'clause.route_rules', 'clause.production', 'clause.scope'})
|
||||
|
||||
|
||||
def support_pins(root, kind):
|
||||
return [excerpt(root, path, *span) if span else source(root, path)
|
||||
for path, span in SUPPORT_POLICY[kind]['sources']]
|
||||
|
||||
|
||||
def authorities(root):
|
||||
"""Selection reads authority text, never generated rows or old audit statuses.
|
||||
@@ -225,15 +264,15 @@ def expected(root):
|
||||
add('chapters.all', 'chapter', [(8,), (26,), (27,30), (256,)],
|
||||
'メインストーリー全章。原本の仕様書章番号をシナリオ章と混同しない。章名・章数・順序・場面一覧は未定義。', 'D08')
|
||||
add('endings.all', 'ending', [(9,), (135,)],
|
||||
'全エンドを要求。個別エンドID・本数・到達条件は未定義。', 'D08')
|
||||
'全エンドを要求。個別エンドID・本数・到達条件は未定義。', 'D08', 'missing')
|
||||
add('endings.bad', 'ending', [(135,)],
|
||||
'特定バッドエンド確定時の敵/背景オブジェクト化。カテゴリのみ原本定義、対象人物/場面/数は未定義。', 'P26-02')
|
||||
'特定バッドエンド確定時の敵/背景オブジェクト化。カテゴリのみ原本定義、対象人物/場面/数は未定義。', 'P26-02', 'missing')
|
||||
add('endings.game_over', 'ending', [(72,)],
|
||||
'赤字決算3回連続のゲームオーバー。物語エンド数への換算不可。', 'P26-02', 'missing',
|
||||
[source(root, 'scripts/economy/economy_service.gd')],
|
||||
[],
|
||||
'欠損: 経済処理の既存コードは存在するが終点のシナリオ・画像・音声・全受入を証明しない。')
|
||||
add('encyclopedia.all', 'encyclopedia', [(9,)],
|
||||
'図鑑コンプ。対象集合・項目ID・解放条件・必要項目数は未定義。', 'D08')
|
||||
'図鑑コンプ。対象集合・項目ID・解放条件・必要項目数は未定義。', 'D08', 'missing')
|
||||
add('dialogue.all', 'dialogue', [(155,), (230,), (256,)],
|
||||
'会話/イベント用台詞とシナリオ。本文・話者・行数・場面単位は未定義。UI通知文を完成脚本に数えない。', 'P26-02')
|
||||
add('portraits.all', 'portrait', [(160,161), (230,), (235,), (242,)],
|
||||
@@ -312,7 +351,6 @@ def expected(root):
|
||||
'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.
|
||||
@@ -322,8 +360,6 @@ def expected(root):
|
||||
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)}
|
||||
@@ -337,34 +373,21 @@ def expected(root):
|
||||
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['scoped_support'] = []
|
||||
for kind, policy in SUPPORT_POLICY.items():
|
||||
supported = sorted(set(matches) & policy['ars']) if support_family(row) else []
|
||||
if supported:
|
||||
row['scoped_support'].append({'kind': kind, 'atomic_requirement_ids': supported,
|
||||
'scope': policy['scope'], 'content_status': 'missing'})
|
||||
row['evidence'] += support_pins(root, kind)
|
||||
supported_ids = {a for claim in row['scoped_support'] for a in claim['atomic_requirement_ids']}
|
||||
row['unsupported_atomic_requirement_ids'] = sorted(set(matches) - supported_ids)
|
||||
if row['scoped_support']:
|
||||
row['status'] = 'partial'
|
||||
row['support_scope'] = 'shared relationship state/container only; no character-specific production or natural completion'
|
||||
row['reason'] += ' 共通関係状態・保存の部分支援のみ。専用イベント/台詞/CG/ボイスは欠損。'
|
||||
row['support_scope'] = '; '.join(c['scope'] for c in row['scoped_support'])
|
||||
row['reason'] += ' scoped_supportに列挙したARの基盤断片のみpartial。他のARは未支援、全制作内容は欠損。'
|
||||
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] = {
|
||||
@@ -373,7 +396,9 @@ def expected(root):
|
||||
'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単位の責任。'),
|
||||
if identity in relevant else 'L226カットイン警告・決闘/防衛演出はP24 UIの所管。制作/受入済みを意味しない。'
|
||||
if identity in {'AR.ui.cut_duel', 'AR.ui.cut_defense'} 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')
|
||||
@@ -416,7 +441,8 @@ def verify_authority(candidate, root):
|
||||
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')
|
||||
'content_status', 'support_scope', 'scoped_support',
|
||||
'unsupported_atomic_requirement_ids'}, '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')
|
||||
@@ -437,8 +463,51 @@ def verify_authority(candidate, root):
|
||||
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')
|
||||
# Inspect submitted claims and pins directly, without expected() or a
|
||||
# generated row. The policy is a normative compatibility table, not a
|
||||
# keyword classifier. A valid hash alone cannot establish relevance.
|
||||
claims = row['scoped_support']
|
||||
require(type(claims) is list, 'support claims type')
|
||||
claimed_ids, kinds, allowed_pins = set(), set(), []
|
||||
for claim in claims:
|
||||
require(set(claim) == {'kind', 'atomic_requirement_ids', 'scope', 'content_status'},
|
||||
'closed support claim')
|
||||
kind = claim['kind']
|
||||
require(kind in SUPPORT_POLICY and kind not in kinds, 'unknown/duplicate support kind')
|
||||
policy = SUPPORT_POLICY[kind]
|
||||
require(support_family(row), 'support incompatible with requirement family')
|
||||
targets = claim['atomic_requirement_ids']
|
||||
require(type(targets) is list and targets and len(targets) == len(set(targets)) and
|
||||
set(targets) <= set(ids) & policy['ars'], 'support incompatible with AR')
|
||||
require(claim['scope'] == policy['scope'] and claim['content_status'] == 'missing',
|
||||
'support scope/content mismatch')
|
||||
kinds.add(kind)
|
||||
claimed_ids.update(targets)
|
||||
for path, span in policy['sources']:
|
||||
pin = source(root, path)
|
||||
if span:
|
||||
pin.update(lines=list(span), text='\n'.join(
|
||||
read_source(root, path).decode().splitlines()[span[0]-1:span[1]]))
|
||||
allowed_pins.append(pin)
|
||||
require(row['unsupported_atomic_requirement_ids'] == sorted(set(ids) - claimed_ids),
|
||||
'unsupported AR partition')
|
||||
require((row['status'] == 'partial') == bool(claims),
|
||||
'route support/content distinction' if row['kind'] == 'route' else 'unqualified partial support')
|
||||
if claims:
|
||||
require(row['support_scope'] == '; '.join(c['scope'] for c in claims) and
|
||||
row['content_status'] == 'missing', 'support summary/content mismatch')
|
||||
else:
|
||||
require(row['support_scope'] == 'no dedicated support established', 'unsupported scope text')
|
||||
if not row['id'].startswith('usage.'):
|
||||
if row['kind'] == 'route':
|
||||
allowed_pins.insert(0, dict(source(root, P19), cell=[
|
||||
row['identity']['character_id'], row['identity']['route_id']]))
|
||||
require(encoded(row['evidence']) == encoded(allowed_pins), 'unrelated or unscoped support evidence')
|
||||
else:
|
||||
require(not claims, 'usage cannot inherit backend support')
|
||||
if row['kind'] in {'voice', 'ending', 'encyclopedia'}:
|
||||
require(row['status'] == row['content_status'] == 'missing' and not row['evidence'],
|
||||
'unsupported content family')
|
||||
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')
|
||||
@@ -497,6 +566,8 @@ def markdown(document):
|
||||
'- 状態: ' + row['status'], '- 所管: ' + row['owner'],
|
||||
'- 内容状態: ' + row['content_status'], '- 部分支援範囲: ' + row['support_scope'],
|
||||
'- AR: ' + encoded(row['atomic_requirement_ids']),
|
||||
'- AR別部分支援(列挙ARの基盤断片のみ、制作内容はmissing): ' + encoded(row['scoped_support']),
|
||||
'- 基盤支援なしのAR: ' + encoded(row['unsupported_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'],
|
||||
|
||||
@@ -145,6 +145,11 @@ class LedgerTests(unittest.TestCase):
|
||||
for row in d['rows']:
|
||||
if identity in row['atomic_requirement_ids']:
|
||||
row['atomic_requirement_ids'].remove(identity)
|
||||
if identity in row['unsupported_atomic_requirement_ids']:
|
||||
row['unsupported_atomic_requirement_ids'].remove(identity)
|
||||
for claim in row['scoped_support']:
|
||||
if identity in claim['atomic_requirement_ids']:
|
||||
claim['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)
|
||||
@@ -173,6 +178,62 @@ class LedgerTests(unittest.TestCase):
|
||||
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')
|
||||
|
||||
def test_closed_support_attribution_independent_controls_and_injection(self):
|
||||
rows = {r['id']: r for r in self.submitted['rows']}
|
||||
audit.verify_authority(self.submitted, ROOT)
|
||||
for identity, kind in [('route.anri.love', 'relationship-container'),
|
||||
('original.line.65', 'night-input'),
|
||||
('original.line.53', 'peeping-container')]:
|
||||
with self.subTest(control=identity):
|
||||
self.assertEqual(rows[identity]['status'], 'partial')
|
||||
self.assertEqual(rows[identity]['content_status'], 'missing')
|
||||
self.assertIn(kind, {c['kind'] for c in rows[identity]['scoped_support']})
|
||||
for identity in ('voice.all', 'portraits.all', 'endings.all', 'encyclopedia.all'):
|
||||
self.assertEqual(rows[identity]['status'], 'missing')
|
||||
self.assertEqual(rows[identity]['evidence'], [])
|
||||
# Valid pins + the old textual qualifier must not confer support.
|
||||
def inject(d):
|
||||
row = next(r for r in d['rows'] if r['id'] == identity)
|
||||
row.update(status='partial', support_scope='backend only',
|
||||
evidence=copy.deepcopy(rows['original.line.53']['evidence']))
|
||||
self.independent_reject(inject, 'unqualified partial support')
|
||||
# Even a well-formed observation claim linked on the SAME source line
|
||||
# cannot be attached to a voice summary.
|
||||
def voice_claim(d):
|
||||
voice = next(r for r in d['rows'] if r['id'] == 'voice.all')
|
||||
mixed = rows['original.line.257']
|
||||
for key in ('status', 'support_scope', 'scoped_support', 'evidence'):
|
||||
voice[key] = copy.deepcopy(mixed[key])
|
||||
self.independent_reject(voice_claim, 'support incompatible with requirement family')
|
||||
# An original-clause row is an eligible family, but its debt-settlement
|
||||
# ARs still cannot receive a peeping-container claim.
|
||||
def content_claim(d):
|
||||
row = next(r for r in d['rows'] if r['id'] == 'original.line.196')
|
||||
donor = rows['original.line.53']
|
||||
for key in ('status', 'support_scope', 'scoped_support', 'evidence'):
|
||||
row[key] = copy.deepcopy(donor[key])
|
||||
row['scoped_support'][0]['atomic_requirement_ids'] = row['atomic_requirement_ids'][:]
|
||||
row['unsupported_atomic_requirement_ids'] = []
|
||||
self.assertEqual(rows['original.line.196']['status'], 'missing')
|
||||
self.independent_reject(content_claim, 'support incompatible with AR')
|
||||
# Mixed rows retain missing voice/end/encyclopedia ARs; an attacker
|
||||
# cannot expand a valid backend claim to those ARs.
|
||||
for identity, atom in [('original.line.257', 'AR.schedule.B-14.3.voice_record'),
|
||||
('clause.production', 'AR.asset.character_voice'),
|
||||
('original.line.9', 'AR.progression.all_endings'),
|
||||
('original.line.9', 'AR.progression.encyclopedia_complete')]:
|
||||
with self.subTest(mixed=identity, atom=atom):
|
||||
self.assertIn(atom, rows[identity]['unsupported_atomic_requirement_ids'])
|
||||
def expand(d):
|
||||
row = next(r for r in d['rows'] if r['id'] == identity)
|
||||
row['scoped_support'][0]['atomic_requirement_ids'].append(atom)
|
||||
row['unsupported_atomic_requirement_ids'].remove(atom)
|
||||
self.independent_reject(expand, 'support incompatible with AR')
|
||||
# A valid claim does not license an additional, unrelated pinned file.
|
||||
self.independent_reject(lambda d: next(r for r in d['rows'] if r['id']=='original.line.65')
|
||||
['evidence'].extend(copy.deepcopy(rows['original.line.53']['evidence'])),
|
||||
'unrelated or unscoped support evidence')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
Reference in New Issue
Block a user