@phyzess/pappra 0.1.0-beta.8 → 0.1.0-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -55,7 +55,9 @@ configured knowledge sources.
55
55
 
56
56
  Optional examples have prerequisites rather than generated files: code-graph and
57
57
  host commands require the selected external CLI/support, and collaboration
58
- requires an explicit controller. They are not needed for the core workflow above.
58
+ requires an explicit controller. `--controller pi-rpc` is the host-directs/Pi-executes
59
+ path, verified end to end against Pi 1.3.0; see [Pi RPC collaboration](docs/pi-rpc.md).
60
+ They are not needed for the core workflow above.
59
61
 
60
62
  ## What it manages
61
63
 
@@ -87,7 +89,7 @@ The check runs the source policy, type checks, formatting, deterministic tests a
87
89
 
88
90
  ## Status and limits
89
91
 
90
- Pappra is development software (`0.1.0-beta.8`) published as `@phyzess/pappra` under the `beta` dist-tag. Releases are published from GitHub Actions with npm trusted publishing (OIDC), so no npm token is stored in this repository. Pin either the moving `beta` tag or an exact version such as `@phyzess/pappra@0.1.0-beta.8`, because pre-release versions are never republished.
92
+ Pappra is development software (`0.1.0-beta.9`) published as `@phyzess/pappra` under the `beta` dist-tag. Releases are published from GitHub Actions with npm trusted publishing (OIDC), so no npm token is stored in this repository. Pin either the moving `beta` tag or an exact version such as `@phyzess/pappra@0.1.0-beta.9`, because pre-release versions are never republished.
91
93
 
92
94
  The deterministic suite covers synthetic workspaces, fixtures and local process boundaries. It does not establish real-model behavior, native host callability on every platform, CodeGraph completeness, Windows/Linux parity or production performance. A host adapter version pin is an observed support claim, not proof that a particular native installation will load or run correctly.
93
95
 
@@ -461,17 +461,6 @@ function currentNativeResult(file, baseline) {
461
461
  return null;
462
462
  }
463
463
  }
464
- function regularFileSize(file) {
465
- if (file === null || !path.isAbsolute(file))
466
- return null;
467
- try {
468
- const observed = fs.lstatSync(file);
469
- return observed.isFile() ? observed.size : null;
470
- }
471
- catch {
472
- return null;
473
- }
474
- }
475
464
  function writeNewFile(file, bytes) {
476
465
  fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
477
466
  fs.writeFileSync(file, bytes, { flag: 'wx', mode: 0o600 });
@@ -1359,10 +1348,14 @@ export class PiRpcController {
1359
1348
  possible_start: stage === 'identity',
1360
1349
  };
1361
1350
  }
1362
- const nativeSessionFile = started.session.sessionFile();
1363
- const nativeSessionBaseline = regularFileSize(nativeSessionFile);
1351
+ let nativeSessionFile = null;
1352
+ let nativeSessionBaseline = 0;
1364
1353
  try {
1365
1354
  await started.session.prompt();
1355
+ // The handle sampled the baseline before it sent the prompt; read it afterwards so
1356
+ // both values describe the launch that just completed.
1357
+ nativeSessionFile = started.session.sessionFile();
1358
+ nativeSessionBaseline = started.session.promptBaseline();
1366
1359
  }
1367
1360
  catch (error) {
1368
1361
  await started.session.finish().catch(() => undefined);
@@ -461,6 +461,8 @@ export declare function startOwnedPiRpc(options: PiOptions): Promise<{
461
461
  pid: number | null;
462
462
  expectedSessionId: string | null;
463
463
  sessionFile: () => string | null;
464
+ /** Byte offset sampled immediately before the prompt was sent. */
465
+ promptBaseline: () => number;
464
466
  /** Read-only native query. Repeatable; always recorded through the protocol. */
465
467
  query(): Promise<{
466
468
  at: string;
@@ -680,6 +680,7 @@ export async function startOwnedPiRpc(options) {
680
680
  let reportedSessionFile = null;
681
681
  let boundSessionFile = null;
682
682
  let promptSent = false;
683
+ let promptBaseline = 0;
683
684
  let lastPromptOrdinal = 0;
684
685
  let operationError = null;
685
686
  let finishPromise = null;
@@ -822,6 +823,8 @@ export async function startOwnedPiRpc(options) {
822
823
  pid: child.pid ?? null,
823
824
  expectedSessionId: admission.expected_identity,
824
825
  sessionFile: () => boundSessionFile,
826
+ /** Byte offset sampled immediately before the prompt was sent. */
827
+ promptBaseline: () => promptBaseline,
825
828
  /** Read-only native query. Repeatable; always recorded through the protocol. */
826
829
  async query() {
827
830
  try {
@@ -894,7 +897,24 @@ export async function startOwnedPiRpc(options) {
894
897
  throw new PiRpcRefusal('prompt_already_sent', 'invalid', 'one frozen prompt per launch');
895
898
  assertNamespaceGuard(opts);
896
899
  assertCurrentAuthority({ requireResources: true });
897
- await assertIdentity({ requireBinding: true });
900
+ // Pi creates the session file lazily, on the first persisted message. The
901
+ // pre-prompt check therefore verifies the reported session id without requiring
902
+ // the header; the post-prompt check requires the bound header before the run is
903
+ // accepted.
904
+ await assertIdentity({ requireBinding: false });
905
+ // The controller publishes only records written after this baseline, so it must
906
+ // be sampled before the prompt creates the first message.
907
+ const baselineFile = reportedSessionFile;
908
+ promptBaseline = (() => {
909
+ if (baselineFile === null)
910
+ return 0;
911
+ try {
912
+ return fs.statSync(baselineFile).size;
913
+ }
914
+ catch {
915
+ return 0;
916
+ }
917
+ })();
898
918
  const ordinalBefore = rpc.recordCount();
899
919
  const response = await request('prompt', { message: opts.promptText });
900
920
  const disposition = response.data?.disposition;
@@ -902,6 +922,24 @@ export async function startOwnedPiRpc(options) {
902
922
  !['started', 'queued', 'handled'].includes(disposition)) {
903
923
  throw new PiRpcRefusal('prompt_not_accepted', 'needs_input', `disposition ${disposition}`);
904
924
  }
925
+ // The header row is flushed just after the prompt is accepted, so bind with a
926
+ // short bounded retry instead of racing the writer.
927
+ const bindDeadline = Date.now() + 2_000;
928
+ for (;;) {
929
+ try {
930
+ await assertIdentity({ requireBinding: true });
931
+ break;
932
+ }
933
+ catch (error) {
934
+ const retryable = error instanceof PiRpcRefusal &&
935
+ (error.code === 'native_session_file_unavailable' ||
936
+ error.code === 'native_session_header_invalid') &&
937
+ Date.now() < bindDeadline;
938
+ if (!retryable)
939
+ throw error;
940
+ await new Promise((resolve) => setTimeout(resolve, 50));
941
+ }
942
+ }
905
943
  promptSent = true;
906
944
  lastPromptOrdinal = ordinalBefore;
907
945
  proof.prompt = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phyzess/pappra",
3
- "version": "0.1.0-beta.8",
3
+ "version": "0.1.0-beta.9",
4
4
  "description": "Portable workspace knowledge and continuity for coding agents.",
5
5
  "publishConfig": {
6
6
  "access": "public"