Skip to content

sethjuarez/web - #89

Merged
Seth Juarez (sethjuarez) merged 2 commits into
mainfrom
sethjuarez/web
Sep 16, 2024
Merged

sethjuarez/web#89
Seth Juarez (sethjuarez) merged 2 commits into
mainfrom
sethjuarez/web

Conversation

@sethjuarez

Copy link
Copy Markdown
Member

No description provided.

Seth Juarez (sethjuarez) added a commit that referenced this pull request Aug 6, 2026
Upstream fix: typra #89 — the Rust backend honoured @entryShorthand on load
but never on save, writing a name-keyed collection entry in expanded form
where the other six backends wrote the shorthand scalar.

Generated delta is small and Rust-only: 3 source files plus the export
surface manifest, +30 lines. The new code is a guarded save-side branch:

  if ctx.use_shorthand && item_data.len() == 1 { ... }

Measured — every runtime matches the 0.4.28 baseline exactly, zero delta:

  Rust        cd runtime/rust && cargo test --workspace --no-fail-fast
              1147 passed / 0 failed
  C#          cd runtime/csharp && dotnet test prompty.sln --nologo
              1356 passed / 0 failed (46 skipped)
  TypeScript  cd runtime/typescript && npm test
              1734 passed / 0 failed (17 skipped)
  Python      cd runtime/python/prompty && uv run pytest tests/ -q
              1484 passed / 0 failed (17 skipped)
  Go          cd runtime/go/prompty && go test -count=1 ./...
              ok

Pin and regeneration travel together in one commit, as with 0.4.28. The
schema-repro-check workflow regenerates from the pinned emitter and fails on
a dirty tree, so a bump without regeneration is not a valid intermediate
state. This is a deliberate deviation from the separate-commits rule.

Verified but NOT fixed here — recorded so it is not rediscovered:

The new save-side shorthand is unreachable in prompty, and its target field
disagrees with the load side.

  1. Reachability. The gate requires item_data.len() == 1. Property always
     serialises `kind` and `enumValues` (as "" and [] when unset), so the
     length is never 1. A probe over four input shapes produced no shorthand
     save in either Rust or Python; both emit byte-identical expanded form:

       {"city": "Seattle"}            -> {"kind":"string","default":"Seattle","enumValues":[]}
       {"city": {"example": "..."}}   -> {"kind":"","example":"Seattle","enumValues":[]}

     So prompty cannot serve as the regression lock for typra #89.

  2. Target mismatch. schema/model/core/properties.tsp:15 declares
     @entryShorthand("default"), and loading a shorthand scalar does place
     the value in `default` — asserted by the
     string_scalar_in_name_keyed_inputs_infers_property vector. But every
     backend's save-side shorthand reads `example`, which is the @Coerce
     target from properties.tsp:18/24/31/37, not the @entryShorthand target:

       Rust  model/core/property.rs   item_data.get("example")
       Go    model/property.go        copy["example"]
       TS    core/property.ts:8       shorthandProperty = "example"
       C#    core/Property.cs:29      ShorthandProperty => "example"

     Uniform across backends, so this is not a cross-runtime divergence — but
     load and save name different fields. Were the length gate ever satisfied,
     a Property carrying only `example` would save as a bare scalar and reload
     with that value in `default`. Reported upstream; not actionable here
     while unreachable.

Also verified: regeneration is idempotent at this pin (a second
`npm run generate` leaves the tree clean), and the pin is exact rather than a
caret so `npm ci` cannot silently absorb a future publish.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Seth Juarez (sethjuarez) added a commit that referenced this pull request Aug 6, 2026
The Python package could not be installed from a clean environment. This
was not a CI flake; `uv pip install -e .` failed outright, and every green
Python number this branch has reported was measured in a virtualenv that
predated the generated pipeline ports.

Reproduced before changing anything, in a fresh venv:

    uv venv <tmp> && uv pip install --python <tmp>/Scripts/python -e .
    -> File ".../prompty/model/pipeline/_EnginePermissionPort.py", line 10
         from prompty.core.cancellation import CancellationToken
       ModuleNotFoundError: No module named 'prompty'

Mechanism: pyproject declares `dynamic = ["version"]`, so flit resolves
__version__ by loading prompty/__init__.py through spec_from_file_location.
That executes the package without putting its parent directory on sys.path,
so relative imports resolve and absolute self-imports do not. The four
generated runtime-cancellable ports were the only absolute self-imports in
the package:

    model/pipeline/_EnginePermissionPort.py
    model/pipeline/_EnginePostCommitPort.py
    model/pipeline/_EngineToolPort.py
    model/pipeline/_Executor.py

Each already imported its generated siblings relatively (`from ..agent._Prompty
import Prompty`) and only the cancellation token absolutely, so the file was
internally inconsistent — the same two-halves-disagreeing shape as typra #78,
#87 and #89.

Fix is configuration, not an emitter change. `cancellationTokenPath` is an
existing emit-target option; the emitter only splits it on the last dot
(python/emitter.js:107-118), so a relative dotted path passes through intact.
Generated ports live at prompty/model/pipeline/, so `...` is the package root.

Not escalated to typra, because the cheap experiment settled it: one config
line and a regeneration, versus an emitter release. Worth recording for them
anyway that the default is absolute and that the adjacent context import in
the same function is already depth-aware (`group ? ".." : "."`) while the
cancellation import is not — so the default will keep breaking any consumer
whose packaging imports the module. That is an upstream defect; it is simply
not one we need a release for.

Verified in a clean venv, which is the environment that previously failed:

    uv pip install -e ".[dev,all]"   ok
    python -c "import prompty"       ok (2.0.0b3)
    ruff check .                     All checks passed
    python -m pytest tests/ -q       1484 passed, 17 skipped, 0 failed

Same 1484 as before, but now reproducible from the manifest rather than from
a pre-existing local virtualenv.

Regeneration remains idempotent: a second `npm run generate` leaves the tree
clean, so the reproducibility gate still holds. Schema config and its
regeneration travel together for that reason.

Not verified: Rust, C#, TypeScript and Go were not re-run. This change is
scoped to the Python emit target and touches no other runtime's output.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant