import type { Cell } from "../cell.ts"; import { createCell } from "../cell.ts"; import type { Runtime } from "../runtime.ts"; import type { IExtendedStorageTransaction } from "../storage/interface.ts"; import type { CellScope } from "../builder/types.ts"; import type { NormalizedFullLink } from "../link-types.ts"; import { resolveLink } from "../link-resolution.ts"; import { linkResolutionProbe } from "../storage/reactivity-log.ts"; import { narrowestScope, scopeRank } from "../scope.ts"; import { createSigilLinkFromParsedLink, getMetaLink, parseLink, } from "../link-utils.ts"; export function resolvedCellScope( runtime: Runtime, tx: IExtendedStorageTransaction, cell: Cell, ): CellScope { return resolveLink(runtime, tx, cell.getAsNormalizedFullLink()).scope; } export function scopedCell( runtime: Runtime, tx: IExtendedStorageTransaction, cell: Cell, scope: CellScope, ): Cell { const link = cell.getAsNormalizedFullLink(); if (link.scope === scope) { return cell; } return createCell(runtime, { ...link, scope }, tx); } /** * The position-derived identity coordinates (`space`/`id`/`path`) of a node's * resolved output binding — the stable, program-independent cause that * map/filter/flatMap key their result container on (CT-1623). Deliberately * drops `scope`/`schema` so the identity stays scope-independent (a scoped vs * unscoped container at the same spot share one id; scope re-addresses the * instance, it does not fork identity). */ export function outputSpotFromBinding( binding: NormalizedFullLink | undefined, ): { space: string; id: string; path: readonly unknown[] } | undefined { if (!binding) return undefined; return { space: binding.space, id: binding.id, path: [...binding.path] }; } export function cellIdentityKey(cell: Cell): { dedupKey: string; linkKey: readonly unknown[]; } { const { space, id, path, scope } = cell.getAsNormalizedFullLink(); const linkKey = [space, id, scope, path] as const; return { dedupKey: JSON.stringify(linkKey), linkKey, }; } export function narrowestCellScope( runtime: Runtime, tx: IExtendedStorageTransaction, cells: Iterable | undefined>, ): CellScope { return narrowestScope( Array.from( cells, (cell) => cell === undefined ? undefined : resolvedCellScope(runtime, tx, cell), ), ); } /** * If the cell contains a top level link, and its scope is narrower than the * cell's scope, create a copy of the cell. * * Copy over the value and the "result" meta link. */ export function exposedResultCell( runtime: Runtime, tx: IExtendedStorageTransaction, cell: Cell, ): Cell { // Ideally, we'd just call getRaw on the cell, but since that may be a link, // we need to know the base to use to parse that link. const target = resolveLink( runtime, tx, cell.getAsNormalizedFullLink(), "writeRedirect", ); const initialCell = cell.withTx(tx); // Identity probe: the raw value is only link-parsed to decide which // target the exposed cell should point at — content is never consumed // (a non-link value just fails the parse). Run it under the // link-resolution-probe scope so flow-label derivation treats it as // link topology, not a content read (S16 — without this, a list // coordinator rebuilding its output array re-consumes every reused // element result's label and smears it across fresh elements). const raw = tx.runWithAmbientReadMeta( linkResolutionProbe, () => initialCell.getRaw({ lastNode: "writeRedirect" }), ); // If the last writeRedirect target is a link, use that, but otherwise use // the last writeRedirect target. const link = parseLink(raw, target) ?? target; if ( link === undefined || scopeRank(link.scope) <= scopeRank(cell.getAsNormalizedFullLink().scope) ) { return cell; } const exposed = scopedCell(runtime, tx, cell, link.scope); // Copy the value and result linkage into the new exposed cell const resultLink = getMetaLink(initialCell, "result"); if (resultLink !== undefined) { exposed.setMetaRaw( "result", createSigilLinkFromParsedLink(resultLink, { base: exposed, includeSchema: true, }), ); } const value = initialCell.get(); if (value !== undefined) { exposed.withTx(tx).set(value); } return exposed; }