What
Found while landing #2367 (PR #2369), and deliberately left out of it.
#2367 documented the three fold depth bounds and asked that each exhaustion produce a [fold:run] fallback with a message naming the bound. Two of the three already did: MAX_FUNCTION_CALL_DEPTH (32, packages/core/src/fold/fold.ts:524) says "call depth exceeded — is it recursive?", and MAX_RESOLUTION_DEPTH (200, packages/core/src/discovery/fold-import.ts:3301) says "cross-file resolution depth exceeded 200 … almost certainly a resolution bug".
MAX_INTERPRETATION_DEPTH (16, packages/core/src/discovery/fold-import.ts:1773) does neither. On exhaustion, resolveInterpretableFactory returns undefined, which its documented contract defines as "not interpretable, never broken". The caller treats that as "invoke rather than interpret", and the file still folds. So exhausting this bound does not fall back to run, does not name the bound, and leaves no trace — it silently changes how the factory is evaluated while reporting the file as folded.
Why it is a spec gap and not a style choice
The specification's F-Depth (INTENTIUS/typescript-as-data spec/judgments.md, J4) has two halves: an implementation must state its bounds, and on exhaustion must fall back rather than fail. #2369 satisfies the first for all three. For this bound the second is unmet in a specific way: it neither falls back nor fails — it degrades. A reviewer cross-checking against the reference implementation will find chant reports "folded" for a file the reference reports "fell back at depth 16", and nothing in chant's output explains the difference.
Why #2369 did not change it
Three reasons, all recorded there: resolveInterpretableFactory's "undefined means not interpretable, never broken" contract is documented and relied on; the depth path has no test coverage, so a behavioural change would land blind; and forcing a throw there is a real change to the fold verdict for any project with a 16-deep composite chain, which none of the corpus has, so the differential would not catch a regression. Documenting the asymmetry was the honest move for a docs PR.
Do
Decide which of two things resolveInterpretableFactory should do at depth 16, and make it do that loudly:
- Fall back, matching the other two bounds: return a
[fold:run] reason naming MAX_INTERPRETATION_DEPTH, so the file goes to run and the operator sees why. This is what F-Depth asks for and what the other two already do.
- Keep degrading to invocation, but say so: a
[fold:degraded]-class diagnostic naming the bound, so the fold verdict is still "folded" but the trace records that interpretation was cut off. This preserves today's behaviour and makes it visible.
Either way: a test that builds a 17-deep composite chain and asserts the outcome by name (red today — there is nothing to assert on), and a fold-adversarial corpus entry for it so the differential covers the shape (#2347 is adding that corpus).
Update the section #2369 added to concepts/typescript-as-data.mdx to match whichever is chosen; the depth-bound guard (scripts/fold-depth-bound-claims.test.ts) pins the values, not the behaviour, so it will not catch a prose claim going stale on this.
Refs #2367, #2369, #2347, INTENTIUS/typescript-as-data#46 (F-Depth).
What
Found while landing #2367 (PR #2369), and deliberately left out of it.
#2367 documented the three fold depth bounds and asked that each exhaustion produce a
[fold:run]fallback with a message naming the bound. Two of the three already did:MAX_FUNCTION_CALL_DEPTH(32,packages/core/src/fold/fold.ts:524) says "call depth exceeded — is it recursive?", andMAX_RESOLUTION_DEPTH(200,packages/core/src/discovery/fold-import.ts:3301) says "cross-file resolution depth exceeded 200 … almost certainly a resolution bug".MAX_INTERPRETATION_DEPTH(16,packages/core/src/discovery/fold-import.ts:1773) does neither. On exhaustion,resolveInterpretableFactoryreturnsundefined, which its documented contract defines as "not interpretable, never broken". The caller treats that as "invoke rather than interpret", and the file still folds. So exhausting this bound does not fall back to run, does not name the bound, and leaves no trace — it silently changes how the factory is evaluated while reporting the file as folded.Why it is a spec gap and not a style choice
The specification's
F-Depth(INTENTIUS/typescript-as-dataspec/judgments.md, J4) has two halves: an implementation must state its bounds, and on exhaustion must fall back rather than fail. #2369 satisfies the first for all three. For this bound the second is unmet in a specific way: it neither falls back nor fails — it degrades. A reviewer cross-checking against the reference implementation will find chant reports "folded" for a file the reference reports "fell back at depth 16", and nothing in chant's output explains the difference.Why #2369 did not change it
Three reasons, all recorded there:
resolveInterpretableFactory's "undefined means not interpretable, never broken" contract is documented and relied on; the depth path has no test coverage, so a behavioural change would land blind; and forcing a throw there is a real change to the fold verdict for any project with a 16-deep composite chain, which none of the corpus has, so the differential would not catch a regression. Documenting the asymmetry was the honest move for a docs PR.Do
Decide which of two things
resolveInterpretableFactoryshould do at depth 16, and make it do that loudly:[fold:run]reason namingMAX_INTERPRETATION_DEPTH, so the file goes to run and the operator sees why. This is what F-Depth asks for and what the other two already do.[fold:degraded]-class diagnostic naming the bound, so the fold verdict is still "folded" but the trace records that interpretation was cut off. This preserves today's behaviour and makes it visible.Either way: a test that builds a 17-deep composite chain and asserts the outcome by name (red today — there is nothing to assert on), and a
fold-adversarialcorpus entry for it so the differential covers the shape (#2347 is adding that corpus).Update the section #2369 added to
concepts/typescript-as-data.mdxto match whichever is chosen; the depth-bound guard (scripts/fold-depth-bound-claims.test.ts) pins the values, not the behaviour, so it will not catch a prose claim going stale on this.Refs #2367, #2369, #2347, INTENTIUS/typescript-as-data#46 (F-Depth).