Composer architecture と managed-state contract¶
参考訳(非正本): この文書は英語版
docs/architecture/composer-mvp.mdの日本語参考訳です。正本は英語版であり、内容または解釈に相違がある場合は英語版が優先されます。
Scope¶
deterministic Composer は、次の public lifecycle を使用します。
inspect -> plan -> apply -> validate
state-changing な composition boundary では operation mode を明示します。
plan/apply --mode initial
plan/apply --mode update
plan/apply --mode upgrade
--mode を省略した場合も initial と同等です。managed-state の update / upgrade を lock の存在だけから推測することはありません。
Lock schema version 2 は consumer intent と provenance の基盤を提供します。read-only reconciliation、crash-recoverable な update apply、explicit upgrade は実装済みです。Update と upgrade は同じ ownership rule と transaction protocol を共有し、upgrade で変わるのは planner がどの compatibility boundary を越えられるかだけです。
Source authority¶
Composer は clean な composition source checkout から実行します。successful な plan/apply はすべて git rev-parse HEAD が返す exact full Git commit に bind され、tracked source modification がある場合は拒否します。
production catalog は closed に load されます。catalog ID は components/ と recipes/ に正確に一致しなければならず、descriptor / recipe は各 schema に対して valid でなければならず、dependency は存在しかつ acyclic でなければならず、generic capability/lifecycle/topology/workspace component は artifact authority に depend してはなりません。
update / upgrade では、old lock の source revision が local source history に存在し、current source revision の ancestor または同一 revision でなければなりません。これにより mutable branch や network を参照せずに downgrade と unrelated-history reconciliation を拒否しつつ、forward reconciliation を許可します。
interrupted recovery では source checkout が transaction に記録された target revision と正確に一致する必要があります。Recovery が異なる source revision に対して暗黙に re-plan することはありません。
Resolution and consumer intent¶
1つの production recipe が artifact と selectable component surface を決定します。Resolution は次から開始します。
artifact
+ recipe.required_components
+ recipe.default_components not explicitly excluded
+ configuration.components.include
続いてすべての requires dependency を transitively に追加します。
resolver は include/exclude overlap、公開されていない selection、required/transitive dependency の exclusion、active conflict、resolved closure に存在しない component を対象とする parameter に対して fail closed します。
consumer configuration は schema version 1 のままです。Lock schema version 2 は、その configuration の normalized semantic snapshot を intent として保存します。
{
"recipe": "skill",
"components": {
"include": [],
"exclude": []
},
"parameters": {}
}
Normalization は include/exclude component ID を lexical order で sort し、parameter 内の object key を recursively に sort しますが、array order は保持します。
update はこの lock intent だけから resolver input を再構築し、--config を拒否します。upgrade は新しい transaction のために explicit configuration を要求し、その normalized intent を新しい lock authority として記録します。
configuration_sha256 は、最後に explicit に与えられた configuration の exact bytes に対する provenance として保持されます。Update は新しい configuration bytes を消費しないため、その digest を carry forward します。Upgrade は exact supplied upgrade configuration の digest で置き換えます。interrupted upgrade recovery では original raw configuration bytes は不要です。transaction がすでに normalized target intent、exact configuration digest、deterministic target lock、exact target source revision を bind しているためです。
Generated materials¶
generated material は bounded declarative な generator ID を指定し、descriptor が executable hook を含むことはありません。initial allowlist は contract-manifest-v1 のみです。
これは resolved closure から contract_registrations を収集し、duplicate identity/path を拒否し、contract ID で sort して deterministic UTF-8 JSON を出力します。未知の generator ID は target write より前に失敗します。
Generated bytes は planning 中に完全に計算されます。そのため managed-state operation では、local-modification protection に関して generated destination を managed destination と同様に扱いつつ、desired bytes は target resolved state から deterministic に再生成されます。
Initial plan and apply¶
Initial plan は read-only です。target を create、adopt-identical、または conflict に分類する前に、すべての copied/generated material bytes を計算します。
Initial composition は既存の異なる bytes を上書きしません。同一の unmanaged file は、結果の lock がその exact bytes を正しく bind できるため adopt できます。portable case collision、file/directory conflict、symbolic-link boundary、unsupported generated-material handler、dependency conflict、既存 managed-state metadata は fail closed します。
作成する file は destination directory 内の temporary file に書き込み、no-overwrite hard-link operation で install します。lock は最後に書き込みます。それ以前に process が停止した場合、repository は unmanaged のままであり、後続の initial apply が adopt できるのは exact に previously materialized な bytes だけです。
Lock schema version 2¶
canonical lock path は引き続き .template-composition/lock.json です。Schema version 2 は timestamp、random value、branch name、network-derived value を含みません。次を bind します。
- canonical source repository identity;
- exact nonzero lowercase 40-hex source revision;
- normalized consumer
intent(recipe、include/exclude selection、parameters); - resolution に使用した exact recipe bytes の SHA-256 (
recipe_sha256); - 最後に supplied された exact configuration bytes の SHA-256 (
configuration_sha256); - lexical order の resolved component ID、positive integer version、exact descriptor-byte SHA-256 value; および
- lexical order の materialized destination、owner、ownership mode、materialized-byte SHA-256 value。
旧 top-level recipe field は、intent.recipe が canonical consumer selection であるため削除されています。Lock schema v1 は意図的に受け付けません。この repository は pre-production であり、backward-compatibility migration は不要です。
recipe digest は v1 の audit gap を閉じます。recipe bytes は resolution に参加するため、component descriptor bytes と同様に lock から識別可能でなければなりません。
seed material では、recorded digest は Composition が最初に供給した bytes を識別します。content ownership は consumer に transfer されるため、consumer-time validation はその後の digest drift を許可します。Update / upgrade は composition に残る seed について、consumer repository に書き込まれていない新しい source-side seed bytes の digest へ置き換えるのではなく、old recorded digest を保持します。
Reserved managed-state metadata¶
Component material は Composer-owned metadata と case-insensitive または structural に collision する path を claim してはなりません。
.template-composition/lock.json
.template-composition/transaction.json
.template-composition/staging/**
transaction.json と staging/** は managed-state recovery protocol のために reserve されています。transaction.json は durable roll-forward marker として実装されています。staging/** は、将来の storage strategy が component destination authority を変更せずに大きな material set を stage できるよう reserve されたままです。
.template-composition/ 配下のその他の file は、lifecycle.composition-state が materialize する self-contained validator と schema を含め、引き続き valid な component destination です。
Consumer-time independence¶
すべての artifact は lifecycle.composition-state を transitively に require します。これは .template-composition/ 配下へ stdlib-only validator と lock schema を materialize します。
consumer validator は source catalog を読みません。lock-v2 shape、source identity、normalized selection constraint、portable/symlink boundary、current material file を検査します。
managed— 存在し、lock digest と一致しなければならない;generated— 存在し、lock digest と一致しなければならない;seed— active に locked されている間は存在しなければならないが、ownership transfer 後の digest drift は許可される。
.template-composition/transaction.json が存在する場合、repository は valid steady state ではなく interrupted managed state として明示的に報告されます。Recovery は source-side Composer operation です。
追加の consumer-owned file は許可されます。特に removed seed は new lock から消え、通常の consumer-owned extra file として残ります。
Update versus upgrade contract¶
managed-state operation は意図的に区別されています。
updateは normalized lock intent を保持し、descendant Composition source revision に対して reconcile する;upgradeは explicit new intent を受け取り、recipe/include/exclude/parameter change や component-version change など、宣言された compatibility-boundary change を許可する。
Composition は general-purpose merge engine ではありません。baseline ownership rule は両 operation に適用されます。
- managed -> managed: current bytes が old lock digest と一致する場合にのみ replace/delete;
- generated -> generated: current bytes が old lock digest と一致する場合にのみ regenerate/delete;
- seed -> seed: current consumer bytes を無条件に preserve;
- new managed/generated: safe かつ unoccupied な destination にだけ create;
- new seed: safe かつ unoccupied な destination にだけ create し、その後の operation では直ちに consumer-owned として扱う;
- removed seed: consumer-owned extra file として preserve;
- existing destination における component-owner または ownership-mode transition: explicit upgrade 中であっても推測しない。
component version change には upgrade が必要です。Update は upgrade-required conflict として報告します。Upgrade は components.changed に記録して継続できます。component が同じ positive integer version のままなのに descriptor digest が変わった場合、source は compatibility-bearing descriptor を version を変えずに変更したことになります。これは source invariant violation であり、update と upgrade の両方が拒否します。
Source material bytes は descriptor change なしに変更できます。これは通常の managed/generated replacement または generated regeneration case です。Seed source-byte change は previously materialized な seed を上書きしません。
source update の間に recipe bytes が変わっても intent.recipe は同じままの場合があります。Update は recipe.from_sha256、recipe.to_sha256、recipe.changed を報告し、それによって生じる component add/remove を explicit に reconcile します。consumer の recipe ID 自体の変更は explicit upgrade intent としてのみ受け入れられ、結果の material graph が existing destination で owner/ownership migration を必要とする場合には引き続き conflict し得ます。
Read-only update reconciliation¶
configuration file なしで update plan を実行します。
python scripts/compose.py plan --mode update --target /path/to/repository
planner は filesystem mutation を行いません。まず v2 lock shape と deterministic ordering constraint を validate し、source identity/revision ancestry を verify し、old normalized intent を再構築し、current catalog を resolve し、desired copied/generated bytes をすべて memory 上で materialize します。
machine-readable plan は次を含みます。
{
"operation": "update",
"from_revision": "...",
"to_revision": "...",
"intent": {},
"recipe": {
"id": "...",
"from_sha256": "...",
"to_sha256": "...",
"changed": false
},
"components": {
"added": [],
"removed": [],
"changed": [],
"unchanged": []
},
"files": {
"create": [],
"replace": [],
"remove": [],
"preserve": [],
"unchanged": [],
"conflict": []
},
"conflicts": [],
"lock_preview": {}
}
top-level intent は update の resolution に使用した old normalized lock intent です。lock_preview.intent は新しく emit される normalized snapshot であり、update では構造上 semantically identical です。
old/new state の両方に存在する destination については次のとおりです。
- managed/generated destination は current digest が old lock と一致した後にのみ
replaceとなり、old/new desired digest が同じならunchanged; - seed は path が safe regular file としてまだ存在することを verify した後、常に
preserve; - component owner または ownership mode が異なる場合は upgrade-required conflict。
newly selected destination では、既存 file、directory、symbolic link、portable case collision、file/directory prefix collision はすべて conflict です。update planner は newly selected material について、bytes が偶然一致していても既存 file を adopt しません。new seed は first materialization 前には consumer-owned instance が存在しないため create action です。その create が成功した後は、以後の operation で consumer-owned seed content として preserve されます。
removed destination では、clean managed/generated file は remove candidate になります。modified managed/generated file は conflict します。removed seed file は preserve entry となって new lock preview から消え、通常の consumer-owned extra file になります。
missing old material は implicit deletion としてではなく invalid old managed state として扱われます。これにより、説明のない consumer-side loss を update が正規化して消してしまうことを防ぎます。
lock preview は current source/recipe/component state を記録し、configuration_sha256 と preserved seed ごとの old recorded digest を carry forward します。
Explicit upgrade reconciliation¶
explicit target configuration がある場合にのみ upgrade planning を実行します。
python scripts/compose.py plan --mode upgrade --config composition.json --target /path/to/repository
plan は完全に read-only で、update と同じ file action class を持ちます。さらに次を報告します。
intent.fromと normalizedintent.to;- old/new exact configuration SHA-256 value;
- old/new recipe ID と recipe digest; および
- explicit
components.changedcompatibility boundary としての component version change。
Upgrade は update と同じ current-byte check を使います。managed/generated local-modification protection を弱めず、existing seed bytes を上書きしません。Include/exclude または recipe change は component とその file を add/remove し得ますが、すべての destination は同じ safe-path、collision、ownership reconciliation を通過します。
同じ destination での ownership-mode transition または component-owner transition は *_TRANSITION_NOT_SUPPORTED として報告されます。Explicit upgrade は consumer が compatibility-boundary change を選択したことを意味しますが、arbitrary existing content を authority 間でどのように migrate すべきか推測するための十分な情報を提供するものではありません。
new normalized intent が parameter だけを変更する場合、file action set が空でも lock は変更され得ます。new lock は exact supplied configuration digest と normalized parameter intent を記録します。
Safe managed-state apply¶
対応する read-only plan を review した後にだけ mutation を実行します。
python scripts/compose.py apply --mode update --target /path/to/repository
python scripts/compose.py apply --mode upgrade --config composition.json --target /path/to/repository
apply は最初の mutation より前に complete plan を再構築します。conflict があれば write を行いません。また planning 中に exact lock bytes が変化していないことも verify します。
non-no-op managed operation では、最初の durable managed-state mutation は .template-composition/transaction.json の作成です。transaction document は次を含みます。
- operation (
updateまたはupgrade) と exact target source revision; - complete old/new lock object;
- exact old lock file bytes と deterministic new lock file bytes の SHA-256 identity; および
- read-only plan から導出された lexical order の
create、replace、removeaction。
transaction schema 自体は full lock schema を複製しません。source-side recovery は embedded lock の両方を composition-lock.schema.json に対して validate し、各 action を対応する old/new file inventory に対して検査します。
各 mutation には precondition があります。
createは absent destination、または recovery 中なら recorded new digest とすでに一致する regular file を受け入れる;replaceは recorded old digest または already-applied new digest のみを受け入れる;removeは recorded old digest または already-absent destination のみを受け入れる;- symbolic link、non-regular file、unsafe parent path、その他の third digest は recovery を停止する。
create は newly selected seed を含む場合があります。replace と remove は seed ownership に対して動作しません。
replacement data は same-directory temporary file に書き込み、fsync し、expected digest を再検査して os.replace で install します。supported な環境では directory metadata も fsync します。Creation は no-overwrite installation semantics を保持します。Deletion の後も supported な環境では directory fsync を行います。
すべての material action の後、lock はその exact file digest が recorded old lock とまだ一致する場合にのみ atomically replace されます。recovery 中は already-installed new lock も受け入れます。その後 source-side self-contained validator function が、transaction marker がまだ存在する状態で new lock と material state を validate します。validation が成功し marker 自体が変更されていないことを verify した後にだけ marker を削除します。
Interrupted update and upgrade recovery¶
protocol は rollback ではなく deterministic roll-forward です。transaction marker 作成後の crash はすべて explicit interrupted state を残します。inspect / consumer validation は recovery required を報告します。
marker に記録された operation を再実行します。
python scripts/compose.py apply --mode update --target /path/to/repository
python scripts/compose.py apply --mode upgrade --target /path/to/repository
Recovery は new plan を構築する代わりに existing transaction を load します。current source checkout が transaction target revision と一致することを要求し、transaction-bound intent と source から deterministic target material/lock state を再構築します。
Upgrade recovery は意図的に --config を受け取りません。marker がすでに chosen target intent と exact new-lock configuration digest を bind しているためです。upgrade recovery 中に config を supplied すると、interrupted transaction と競合する second intent を許すのではなく拒否します。
各 action について、old state は「この action を実行する」、new state は「この action はすでに完了した」を意味します。その他の state はすべて conflict であり、変更しません。したがって action の実行前、または interrupted marker が書かれた後に consumer change が行われても、retry が暗黙に上書きすることはありません。
new lock install 後かつ marker deletion 前の crash も recoverable です。すべての action と lock は already applied と認識され、new state を再度 validate した後にのみ marker を削除します。
same-revision、same-intent の no-op update は transaction marker を書かず、lock も rewrite しません。exact target lock と mutation set がすでに同一である upgrade も同様に write-free です。
Determinism and execution boundary¶
同じ immutable source revision、selected intent、valid old managed state に対して、reconciliation order、managed/generated output bytes、plan JSON、transaction JSON、resulting lock preview は deterministic です。ownership transfer 後の seed content は merge されず preserve されます。
Composer は composition state を導出するとき、mutable branch、wall-clock time、random value、network-discovered default、arbitrary hook、consumer code、package manager、product build/test/deploy command を参照しません。
通常の filesystem tool と同様、coordinated されていない concurrent writer は filesystem syscall と race し得ます。それでも Composer は各 replacement/removal の直前に digest/path precondition を再検査し、unexpected digest を mergeable state として扱うことはありません。
Managed-state work decomposition¶
managed-state implementation は4つの reviewable layer として提供されました。
- lock-v2 と update/upgrade contract;
- read-only update reconciliation planning;
- safe update mutation と interrupted-update recovery; および
- 同じ ownership / transaction protocol を使用する explicit upgrade semantics。
これらの layer により、Composition を general-purpose merge engine にすることなく、lock-contained intent recovery、deterministic read-only planning、digest-guarded managed/generated mutation、seed preservation、explicit compatibility-boundary change、deterministic roll-forward recovery を提供します。