VIP-33: the platform standard library
Status: accepted, 2026-10-29: Will's 10-26 rulings, re-presented against VIP-1 to VIP-32 and recorded under his standing approval of recommendations.
Decision
import { NodeServices } from "@effect/platform-node" // a platform package: imported, not in scope
effect function readConfig(path: string) {
const fs = try FileSystem.FileSystem // Effect's platform service, injected by a layer
return try fs.readFileString(path)
}
effect function main() {
provide NodeServices.layer { try readConfig("app.json") } // VIP-12; the platform layer is chosen at the edge
}Amended 2026-11-03 (VIP-40): The restrictions below apply to VibeLang code. A named top-level module body follows the rules of a sibling .ts file; VIP-40 supersedes VIP-25.
- Platform access only through injected Effect services (
FileSystem,Path,ChildProcess,HttpClient, …), each provided by a per-platform layer. Outside a TypeScriptmodulebody, a.vibefile has nonode:*value imports and no Node globals (process,Buffer). Code that needs them lives in a.tsfile or a named top-levelmodulebody (VIP-40, which supersedes VIP-25).- Amended 2026-10-29: no exception inside
Effect.try/tryPromisearrows. A platform call there, such astryPromise(() => fetch(url)), is the same error as anywhere in.vibe. Its quick fix and the codemod move the body into a sibling.tsmodule, or point to the Effect service (HttpClientforfetch). Recorded under Will's standing approval of recommendations.
- Amended 2026-10-29: no exception inside
- What Effect lacks goes under
Experimental.X, named after the Effect module it extends, and is written so it can be proposed upstream. No per-project wrappers, and nothing that Effect already provides. - Subpath namespaces are in scope (
HttpClient,FileSystem,Path,ChildProcess, … from Effect's subpath packages), with VIP-14's precedence rules. - Old VibeLang's alias
Path.join(…)→Path.Path.join(…)(D311) was a service shorthand, which VIP-10 denied. You write(try Path.Path).join(…). - Old VibeLang's provisional D312, under which the entry provided NodeServices for
main, is gone (VIP-26).
Corrected 2026-11-02: the Decision example imports NodeServices from @effect/platform-node, a platform package that is not in scope (VIP-14), and provides it inside effect function main(), because provide belongs in effect code (VIP-12); chapter 5 shows the same program (found by the round-6 review).
Corrected 2026-10-29: the module FileSystem is a namespace, and the service key it exports is FileSystem.FileSystem (likewise Path.Path); in effect 4.1.1, yield* of the namespace is a defect.
- Amended 2026-10-29: the banned globals. A reference in any
.vibecode to a Node global (process,Buffer,global,__dirname,__filename,require,module,exports,setImmediate,clearImmediate), a web I/O or host-state global (fetch,XMLHttpRequest,WebSocket,EventSource,localStorage,sessionStorage,indexedDB,caches,navigator,location,document,window,self,Worker,SharedWorker,BroadcastChannel,postMessage,alert,confirm,prompt,crypto,performance),consoleor a timer (setTimeout,setInterval,clearTimeout,clearInterval,queueMicrotask) is a compile error whose quick fix names the Effect service or moves the code to a.tsfile. ECMAScript library globals (includingIntl) and web data utilities with no I/O (URL,URLSearchParams,TextEncoder,TextDecoder,structuredClone,atob,btoa,AbortController) stay allowed. Decided by the agent on 2026-10-29 under Will's rule "when I state a principle, the agent derives the concrete rule and acts; I can override later" (decided detail 56). - Amended 2026-10-29: the banned globals are derived, not listed. A value reference in
.vibecode isEK4402when the symbol it resolves to is declared inlib.dom*.d.ts,lib.webworker*.d.ts,lib.scripthost.d.tsor@types/node, not inlib.es*.d.tsorlib.decorators*.d.ts, and is not on the allowlist. A local binding, an import or an Effect module in scope shadows the global first. The allowlist is closed: the web data utilities above plusAbortSignal, which Effect passes toEffect.tryPromisecallbacks;AbortSignal.timeoutstays banned. The set is generated intodocs/design/inventory/platform-globals.json. This replaces the enumerated list above, whose names keep their quick fixes. Decided by the agent on 2026-10-29 under Will's rule "when I state a principle, the agent derives the concrete rule and acts; I can override later" (decided detail 101). - Amended 2026-10-29:
globalThisandimport().globalThismay appear in.vibecode only as the object of a statically named member access. A banned platform member isEK4402;evalandFunctionfollow VIP-1, including itsFunctionexemptions for the right operand ofinstanceofand a statically namedprototypeaccess. Every other use ofglobalThisisEK0107.import(expr)with a computed specifier stays unchecked; a string-literaldata:orblob:specifier isEK0107. Decided by the agent on 2026-10-29 under Will's rule "when I state a principle, the agent derives the concrete rule and acts; I can override later" (decided detail 73 and 74). - Amended 2026-10-29: bare
Worker. BareWorkeris Effect'seffect/workersnamespace, asHeaders,ResponseandEventare Effect's; the bannedWorkerentry covers the host binding only (globalThis.Worker,self.Worker). Decided by the agent on 2026-10-29 under Will's rule "when I state a principle, the agent derives the concrete rule and acts; I can override later" (decided detail 76). - Amended 2026-10-29:
import.metais limited toimport.meta.url. In.vibecodeimport.metamay appear only as the object of.url. Every other member (dirname,filename,env,main,resolve, host additions) and every other use ofimport.meta(an alias, destructuring, a computed key, an argument, a spread) isEK4402; a type position is TypeScript's. The quick fix names the Effect service (Path,Config.String) or moves the code to a.tsfile. Decided by the agent on 2026-10-29 under Will's rule "when I state a principle, the agent derives the concrete rule and acts; I can override later" (decided detail 90). - Amended 2026-10-30: one rule for platform members.
EK4402asks one question of a statically named value, an identifier,.name,?.name, a literal key or a destructured property: does the symbol it resolves to reach the platform, by doing I/O, starting a timer, or reading or changing host state? That is a banned global, or a member of an allowlisted global that reaches the host:AbortSignal.timeout,URL.createObjectURLandURL.revokeObjectURL. An alias keeps the symbol, and a key that is not statically known stays unchecked. A name that resolves to nothing isTS2304.docs/design/inventory/platform-globals.jsonrecords the banned globals, the allowlist and every allowlisted member, classified, and a test fails when one is unclassified. Decided by the agent on 2026-10-30 under Will's rule "when I state a principle, the agent derives the concrete rule and acts; I can override later";URL.createObjectURLandURL.revokeObjectURLfollow from this VIP's "All web apis and all node.js apis should be wrapped and effectful", and no.vibefile in the repository uses them.
Will's words (2026-10-26)
"All web apis and all node.js apis should be wrapped and effectful. In fact, a lot of this is ALREADY wrapped and effectful. Platform should always be dependency injected. Effect has a platform package. If it doesn't support something we should extend it to support it."
"Make sure you do NOT implement anything that is already in effect."
"make Effect namespace global and every namespace below Effect global and if we were forced to add something because Effect doesn't already have a namespace for it it should be under Experimental."
"Node.js should only even be available via dependency injection or via escaping to normal typescript"
- Amended 2026-11-01: fresh platform companion. The platform codemod may create a new owned sibling
.tscompanion only when it preserves run phase, captures, receiver and public types. It never overwrites or edits an existing foreign file; a collision or unavailable preservation proof gives the not-fixed explanation. This is VIP-34's only exception allowing creation of a foreign-source companion.