SlimeRESCUE-AS400 — 検証資料と監査記録

IBM i 実機で何を測ったか、どう測っているか、そして何がまだ実証されていないか。このページの元になった監査で、以前の照合経路が主張どおりのものを比べていなかったことが判明したため、欠陥の記録も修正後の結果と並べて残しています。

1. 以前のハーネスが実際に比べていたもの

最初のハーネス (rescue/run_rescue_diff.sh) は、両辺の stdout と stderr を 結合したものに grep -oE '[0-9]{8,}' | head -1 をかけ、取れた整数どうしを比べていました。 この経路のどこでもバイト列は比較されておらず、8 桁の数字が出ない run では比較対象自体が生じません。

さらに 2 つのステップが黙って失敗していました。出力を捨てるのをやめて初めて見えたものです。

  • DB2 クロスチェックはインライン RUNSQL 形式を使っており、本機はこれを拒否します (SQL0104・戻り値 255・メッセージ ID なし)。出力は /dev/null に捨てられ、 値は PASS 条件にも入っていないため、この照合はどの run でも値を出していなかった可能性があります
  • トランスパイラは DDS がソースの隣に無いと別のプログラムを出力します (レコード項目の型が失われる)。 それを暴くコンパイル失敗も同様に捨てられていました。これは製品側の欠陥であり、3 節に欠陥として記録しています。

2. 修正版ハーネスがしていること

rescue/run_rescue_diff_v2.sh。旧版は変更していません — 監査の対象そのものであり、 上書きは証拠の破壊にあたるためです。

  • 宣言された観測を三層。いずれも数字の grep ではありません: 完了メッセージの全文 / データメンバの生バイト (CCSID 変換なし・run 前後) / 回収したソースメンバの生バイト。
  • ソース属性はオブジェクトから読むQSYS2.PROGRAM_INFO であり、 RTVCLSRC が出力冒頭に印字するテキストではありません。列名は記憶ではなく、毎 run QSYS2.SYSCOLUMNS から取り直します。
  • DB2 は PASS 条件の中にあります。横に置いてあるのではありません。
  • どのステップの失敗でも run が止まります。 戻り値を検査し、非ゼロが許されるのは そのステップの出したメッセージ ID が全て明示の benign 一覧にある場合だけです。未知の ID は失敗します。
  • env が無ければ PASS しません。 ジョブの CCSID・日付書式・ライブラリリストを 最初に取得し、取れなければ結果を報告することを拒否します。

3. 検証済みの数値 Open

この節の数字はすべて claims/emit_claims.py が実測から生成しています。手で書いた数字はありません。生成されなかった項目は空欄になります(古い数字が残ることはありません)。

exists-over-tested = 測定した範囲で成立した、という意味であり、全入力に対する主張ではありません。

source deleted, then recovered from the object: the recovered CL is the same command stream that was compiled as400-source-loss-recovery-20260727

programs3
negative_controls_failedTrue
read_after_delete_messageCPFA0A9 Object not found
source_recovered3
command_stream_equal_after_normalisation3/3
compiled_fromsource member (CRTBNDCL SRCFILE/SRCMBR)
module_attributes['RM1SUM,RM1SUM,BARON1,QCLSRC,RM1SUM,', 'RM2DEC,RM2DEC,BARON1,QCLSRC,RM2DEC,', 'RM3MSG,RM3MSG,BARON1,QCLSRC,RM3MSG,']
保証レベルS
量化exists-over-tested
測定域programs=3, constructs=zoned-decimal file summation, packed decimal with a fraction, MONMSG on a real message, library=BARON1
観測対象the source member read before deletion, the same read attempted after RMVM (required to fail), the member's absence in the file system, the recovered source's command stream after normalising RTVCLSRC formatting
オラクルIBM i on PUB400 IBM i V7R5M0 execution
期待値の出所実機で確定
findingthis is the first run in which the source was actually gone. The negative control is the load-bearing part: after RMVM the same read fails with CPFA0A9 and the member is absent from the file system, so the recovery cannot have come from the source file. The programs were compiled the traditional way, so QSYS2.BOUND_MODULE_INFO records SOURCE_FILE_LIBRARY/FILE/MEMBER pointing at a member that no longer exists — 'recorded but gone', which is a different state from the stream-file case's 'recorded as a path'.
caveatequality is of the command stream after normalising RTVCLSRC's formatting, not of bytes: RTVCLSRC re-emits its own header, column positions and line wrapping, so byte equality is not available on this path and is not claimed. Recovery depends on the program having been created with its source retained; a *PGM compiled without that cannot be rescued this way, and that case was not tested. N=3 programs. Whether the recovered program then behaves identically is a separate row — it does not, for two of the three.
audit_hashsha256:2c1e3692d8081b4d5275e3895cec13f3293ebb52eaec16375a019f4f35b2dbae

the recovered programs, transpiled and run: 3 of 3 match the live result as400-loss-execution-20260727

programs3
agreeing3
job_decimal_formatJ
保証レベルS
量化exists-over-tested
測定域programs=3
観測対象the completion message text in full
オラクルIBM i on PUB400 IBM i V7R5M0 execution
期待値の出所実機で確定
findingfirst measured at 1 of 3, and the two failures were transpiler gaps rather than rescue failures — the source had recovered correctly in all three cases. Both gaps were then closed and the run repeated: the job's decimal format became a declared input (RM2DEC), and CPF9801 became a modelled OS effect so that CHKOBJ raises it and MONMSG catches it (RM3MSG). The figure here is from the repeat. Keeping the intermediate state visible matters more than the final number: a row that had only ever said 3 of 3 would not show that the environment had to be declared to the tool before the tool could reproduce the machine.
caveatthree programs, one job, one machine. Agreement here is under the declared observation — the completion message text — and under a declared environment: CCSID 273, DECFMT J. Change the job and the same programs may not agree; that is a property of the claim, not a defect. This row and the recovery row are kept separate because they answer different questions: joined into one figure, a transpiler gap would read as a failed rescue.
audit_hashsha256:0a0d48d5f47e6dd3083492579f3853f49d55e13c1b49b73a29d74e31eeed88ad

one effect lattice across SKUs; an unmodelled OS effect is declared, not dropped os-effect-lattice-20260727

registryclaims/os_effects.json
effects_registered4
ibm_i_modelled['CPF0864', 'CPF9801']
vos3_declared_unmodelled['PR']
registry_matches_emitterTrue
drift[]
unmodelled_effect_behaviourrefused at emit time, exit 6
保証レベルB
量化for-all-emissions
測定域form=OsContext(kind, id), instances=VOS3 svc(n) / IBM i msg(msgid), cl_sources_in_repo=43, refused=1
観測対象what the emitter does with a MONMSG whose message is not in the table
オラクルthe registry and the emitter source static
期待値の出所リポジトリとの整合確認
findingthe third fail-open found in this emitter. An unmodelled MONMSG was written out as a comment, so a program whose control flow depends on catching an escape message ran as if the message never happened — RM3MSG said NOTHING-CAUGHT where the machine said CPF9801-CAUGHT. The pattern across all three (DCLF, DECFMT, MONMSG) is the same: an undeclared part of the environment gets a default and execution continues. The lattice replaces the default with a registry, and absence from the registry with a refusal.
caveatthe table holds one measured IBM i effect (CPF9801) plus the two older paths (CPF0864, and CPF0000 in the RUNSQL position only). That is deliberately small: an entry requires a measurement, not a recollection. Scope for MONMSG itself was cut to a command-level monitor with a single message id and a non-branching EXEC; branching monitors, generic class monitors and message replacement variables are refused, and one source in the repository (DBGWRAP.clp, a bare CPF0000) is refused as a result.
audit_hashsha256:22ba28f06b5f0bf525e4ce660038ae1796316f7dd0ce0a23a9d18a8c2cf523d4

a *PGM's CL source, recovered from the object by RTVCLSRC, reproduces the live program's result as400-cl-rescue-20260727

live_message00000000425
candidate_message00000000425
db2_sum425
equalTrue
custf_member_sha2560000f587b1dc1caef13cd85689b0e909
custf_unchanged_by_runTrue
recovered_member_sha2563ecfd78752d3b0ac1a83face50d384f1
source_file_attributes_on_object['', '', '', '']
n_programs1
n_records3
保証レベルS
量化exists-over-tested
測定域programs=1, records=3, expected_sum=425, program=P5DRV (CLLE, reads a PF and sums a zoned field)
観測対象the completion message text in full, compared as a string, the CUSTF member's raw bytes before and after the run (no CCSID conversion), the recovered source member's raw bytes
オラクルIBM i on PUB400 IBM i V7R5M0 execution
期待値の出所実機で確定
未モデルthe job log beyond the completion message
spool
MSGID
findingCORRECTED 2026-07-27: the source attributes were read from QSYS2.PROGRAM_INFO, and those columns are empty for an ILE *PGM no matter how it was compiled — a control compiled the traditional way (SRCFILE/SRCMBR) shows them empty too. The origin of an ILE program is recorded on the bound module: in QSYS2.BOUND_MODULE_INFO this program carries SOURCE_STREAM_FILE_PATH = the .clp it was compiled from, while the control carries SOURCE_FILE_LIBRARY/FILE/MEMBER instead. So the audit's reading holds and is in fact stronger — the object records where its source came from either way, and the empty member columns say 'compiled from a stream file', not 'source lost' — but the evidence first published for it was read from the wrong view. Two defects in the old path also showed up under measurement: the inline RUNSQL form v1 used for the DB2 cross-check fails on this machine (SQL0104, no message id, rc=255 discarded to /dev/null), so that cross-check may never have produced a value; and SlimeCL silently emits a different program when the DDS is not beside the source, which only surfaces because rustc refuses to compile it — v1 discarded rustc's output too.
caveatvalue equality under the declared observation, not byte-exactness: the live side's message crosses a CCSID conversion and the candidate is a native binary, so the two are not byte-comparable and this claim does not pretend otherwise. N=1 program, 3 records. The source member was present for the whole run: nothing here shows a lost source can be recovered. The member hash pins this run's input; it is not stable across runs, because deleted-record space makes the member grow.
supersedesas400-cl-rescue-20260726
audit_hashsha256:cb5bb38685b61a14be08e828dba9130c158a9f13543212536c639ba74cba2a5f

CL rescue: *PGM -> RTVCLSRC -> SlimeCL -> Rust vs the live CALL as400-cl-rescue-20260726

comparisonvalue equality of a 3-digit integer (425)
both_sides_viagrep -oE '[0-9]{8,}' | head -1
candidate_normalised_byCR and trailing-whitespace strip
bytes_compared0
db2_cross_check_in_pass_conditionFalse
n1
保証レベルpending
量化exists-over-tested
測定域programs=1, records=3, expected_sum=425
観測対象the first 8+ digit run in the combined stdout and stderr
オラクルIBM i 7.5 on PUB400 execution
期待値の出所phantom-caught
未モデルjob log
MSGID
exceptions
spool
blocked bykept as the record of what the v1 path measured, which is one integer pulled out of a text stream by grep. It is superseded rather than deleted because the defect is the point: the figure it produced was never worth publishing, and two of its steps are now known to have been failing silently. Replaced by as400-cl-rescue-20260727, measured through rescue/run_rescue_diff_v2.sh on 2026-07-27. What neither row shows is a lost source being recovered — that needs the RMVM experiment, with the failed source lookup as a negative control, and it has not been run.
findingthe empty 'original source file' slot that the world-first wording rested on is produced by compiling from a stream file (SRCSTMF) in this same script. It is a consequence of the compile method, not evidence that a source was lost.
audit_hashsha256:b6ec96fa2c4dc16c772fdfd326e9faec2306a6dcdc6018201a310a150bc5c13a

DECFLOAT(34) data rescue: the candidate's record bytes equal the live member's, byte for byte as400-data-rescue-p19-20260727

bytes_compared88
identicalTrue
ibm_i_member_sha2567d28c344ff862107c55480e171b3a7c7
candidate_sha2567d28c344ff862107c55480e171b3a7c7
member_changed_by_live_runTrue
records4
files1
保証レベルS
量化exists-over-tested
測定域files=1, records=4, bytes=88, types=CHAR(6) and DECFLOAT(34) as decimal128 DPD
観測対象the whole member's bytes, copied with no CCSID conversion, compared as bytes rather than as values
オラクルIBM i on PUB400 IBM i V7R5M0 execution
期待値の出所実機で確定
未モデルrecords beyond this member
other DECFLOAT encodings
findingcomparing DECFLOAT as DPD bytes rather than as decimal values is what makes this the strict path: a candidate that is value-equal but byte-unequal cannot pass. The harness now also refuses to report a result if the live program left the member unchanged, which would have made the comparison vacuous.
caveatthe comparison is byte-level; the guarantee level is not. S means a differential test against an oracle over the domain that was tested — one file, four records, one job whose CCSID is 273. It is not a statement about all DECFLOAT values or all members. 'byte-exact' describes what was compared, not how far it generalises.
supersedesas400-data-rescue-p19-20260726
audit_hashsha256:e6239c55b296fc9ecabc88adcee28859d4a766a6a8cfecdc1b1a1ce147d56b76

data rescue: DECFLOAT(34) member bytes vs the live machine as400-data-rescue-p19-20260726

comparisonhex of the whole physical-file member
member_obtained_bycp from /qsys.lib (no CCSID conversion)
decfloat_compared_asDPD bytes, not values
保証レベルpending
量化exists-over-tested
測定域harness=p19/run_p19dg_diff.sh
観測対象mem[whole member] as hex
オラクルIBM i 7.5 on PUB400 execution
期待値の出所measured-on-disk
blocked bythe method is sound — raw member bytes, no conversion, DECFLOAT compared as DPD bytes rather than values, so value-equal/byte-unequal cannot slip through — but it has not been re-run under audit. The row carries the method, not a result.
audit_hashsha256:e6830c60447a9ffb1dde01d0a67800a67783e8865460ef589bdeb7ffd5ad0d8f

audit of the comparison path, from the source of the harnesses as400-harness-audit-20260726

scripts_read2
defects_in_cl_harness9
errors_discardedevery remote system call redirects to /dev/null
set_eFalse
library_hardcoded_atrun_rescue_diff.sh:56
gate_read_fromEnglish header string in CCSID-1208 text
保証レベルB
量化for-all-lines-read
測定域files=['run_rescue_diff.sh', 'run_p19dg_diff.sh']
観測対象the source of the harness
オラクルthe harness source static reading
期待値の出所リポジトリとの整合確認
findingthe strongest wording in the product sits on the weakest comparison in the repository, and the strongest comparison already exists a few directories away in p19.
audit_hashsha256:ea2516e469f4f950cb7ad859778cae200f120ccdb3fa7f36bef09ed4e725de9b

SlimeCL emitted a different program without a diagnostic when the DCLF's DDS was missing — removed 2026-07-27 slimecl-silent-dclf-degradation-20260727

functiondds::resolve_dclf
signals_on_unresolved_ddsTrue
caller_refuses_to_emitTrue
exit_code_on_refusal4
regression_tests3
detected_byrustc rejecting i64 + String, once the harness stopped discarding compiler output
fixedTrue
保証レベルB
量化for-all-emissions-with-unresolved-dclf
測定域file=src/dds.rs, function=resolve_dclf, observed_case=rescue/out_v2 without CUSTF.PF beside the source
観測対象the emitted Rust's declaration of the DCLF fields
オラクルthe repository source static
期待値の出所リポジトリとの整合確認
findinga defect row, not a result. It is here because a fail-open path in the product is worth more attention than a passing figure: every Slime-family claim that runs through this transpiler inherits it, SlimeCOBOL included.
caveatthe check now has two outcomes and no third: the DDS was found and read, or the file is named in a refusal that exits 4. Three regression tests hold that shape, because a fallback is easy to reintroduce as a convenience. An explicit --allow-unresolved-dclf remains for the case where untyped fields are genuinely intended; it has to be typed, which is the difference between a decision and a default. The harness-side defences stay in place — layered, because the product fix and the harness check fail differently. The first version of the check counted fields rather than files and refused two valid programs whose display file holds only constants; resolution now means the DDS was found.
audit_hashsha256:03b6eae4fd5b2c09197fca31cf951a93abdb476db8416f0882ed7e142b49518f

SlimeCL rendered decimals with a period regardless of the job's DECFMT — now a declaration, refused when absent slimecl-decfmt-unmodeled-20260727

job_decfmtJ
ibm_i000000350,80
slimecl000000350,80
agrees_nowTrue
before_the_fixIBM i 000000350,80 vs SlimeCL 000000350.80
declaration_requiredTrue
exit_code_when_undeclared5
sources_in_repo_needing_a_declaration4
fixedTrue
保証レベルB
量化exists-over-tested
測定域case=CHGVAR of *DEC(9 2) into *CHAR, job DECFMT=J, sources_needing_declaration=4, sources_unaffected=39
観測対象the character rendering of a packed decimal
オラクルIBM i on PUB400 execution
期待値の出所実機で確定
findingthe second fail-open found in this emitter, after the DCLF one: both are an undeclared environment being given a default and carried on with. Two of a kind is a reason to look for a third, so an inventory of every environment-dependent default in SlimeCL is queued behind the MONMSG work. The job on this machine has DECFMT J, so CL writes 350,80 while the emitter wrote 350.80 and did not fail. The separator is now a declared model commitment: --decfmt J maps to a comma because that is what was measured here, comma and period are direct declarations by the caller, and an IBM i DECFMT value that has not been measured is refused rather than guessed. A program that renders a fractional decimal without a declaration does not compile — exit 5. With the harness now passing the job DECFMT it measured, this case agrees: the loop from 'environment observed on the machine' to 'environment declared to the tool' is closed.
caveatonly J was established on hardware. period and comma are declarations, not measurements, and are labelled that way in the source. The first version of the refusal matched the helper's definition rather than its call sites and rejected all 43 CL sources in the repository; the check now looks at call sites, and 4 sources genuinely render a fractional decimal while 39 are untouched.
audit_hashsha256:0fec29184dda4d80526e20fa65f34695f1389732dd265639616dd713128a3a30

生成: 2026-07-27T04:11:52Zbitexact/_AS400_AUDIT_2026-07-26/emit_claims_as400.py

4. 主張していること・していないこと

ソース喪失実験は実施しました (2026-07-27)。3 本のプログラムをソースメンバからコンパイルし、 メンバを RMVM で削除し、削除前に成功していた読み取りを再度試みて 3 本とも CPFA0A9 で失敗することを確認しています — この負の対照があって初めて、 削除は主張ではなく事実になります。そのうえで各オブジェクトからソースを回収し、 3 本すべてで回収された CL がコンパイルしたものと同じコマンド列であることを確認しました。

回収したプログラムを走らせた結果は別の問いです。最初の測定は 3 本中 1 本、現在は 3 本中 3 本 — そして読む価値があるのはこの 2 つの数字の間の経路です。どちらの不一致も救済の失敗ではありません。 ソースは最初から 3 本とも正しく回収されており、失敗していたのはトランスパイラが実機の環境を 再現できていなかった点です。ジョブは DECFMT J なので IBM i は 350,80 と書きますが、 トランスパイラは 350.80 と書いていました — 分離子を既定値ではなく宣言された入力に変え、 ハーネスが測定した書式をそのまま渡すようにしました。存在しないオブジェクトへの CHKOBJ は CPF9801 を上げ MONMSG が捕まえますが、トランスパイラはこれを何もしないものとして扱っていました — CPF9801 は登録された OS 効果になり、登録に無い効果は emit 時に拒否され、コメントとして 書き出されることはなくなりました。

数値は宣言された観測の下での値の一致です。CL 経路ではバイト一致ではありません — 実機側のメッセージは CCSID 変換を経ており、候補側はネイティブバイナリです。回収ソースは RTVCLSRC 自身の書式を正規化したうえでコマンド列として比較しています。 バイト一致はこの経路では得られず、主張もしていません。

訂正 (2026-07-27): 本ページの以前の版は、QSYS2.PROGRAM_INFO の ソースファイル列が空であることを、コンパイル方法についての証拠として挙げていました。 この列は ILE プログラムではコンパイル方法によらず空であり、出所は束ねられたモジュール側に記録されます。 QSYS2.BOUND_MODULE_INFO で読むと、ストリームファイルからのコンパイルはそのパスを、 メンバからのコンパイルはライブラリ・ファイル・メンバを記録しています。結論は変わらず、 むしろ強くなりますが、最初に挙げた根拠は誤ったビューから読んだものでした。

救済はソース保持付きで作成されたプログラムであることに依存します。それ無しでコンパイルされた *PGM はこの方法では回収できず、その場合は未検証です。N=3 本。

5. 先行性について — 正確な scope での「世界初」

ソースメンバが実際に削除された IBM i の *PGM から、削除を負の対照で立証したうえで ソースを復元してみせた公開実証を、我々は他に知りません。 これが主張であり、その scope は 測定した範囲そのものです: ソース層・3 本・削除は事後に同じ読み取りを試みて 3 本とも CPFA0A9 を 得ることで立証。

この文の性質について 2 点。本ページの他の記述は測定から組み立てられていますが、これは違います。

  • 先行性はここでは測定できません。 本ハーネスが立てられるのはこの機械で 何が起きたかであって、他の誰が何をしていないかは決して立てられません。上の一文は 我々の知る限りの公表記録についての言明であり、その性質のものとして書いています。 先行する実証を示されたら正しい対応は撤回であり、他を書き換えずに撤回できる形にしてあります。
  • scope はソース層に限られます。 回収したプログラムが元と同じ動きをする、 という主張ではありません — 3 本中 1 本がそうで、そうでない 2 本は理由とともに上に記録しています。 以前の本サイトは救済経路全体に「world-first」を付けていましたが、その文言は自社監査により その裏付けが 3 桁整数の値一致であったと判明したため 2026-07-26 に取り下げており、復帰させません。

6. 関連