# TinyImport agent and backend workflow — v1

TinyImport is available in bounded Preview and Free projects. Check the current
discovery endpoint and your project limits before using these commands.
Production integrations require owner approval and authenticated backend setup.

TinyImport turns UTF-8 CSV into validated, human-reviewed batches that your
backend pulls. Your database remains authoritative. TinyScale never needs your
database credential, runs your arbitrary transforms, or approves customer writes
on behalf of an agent. Package names below refer to private repository packages,
not published npm distributions.

## Agent setup with a synthetic file

Download and verify the current CLI using https://api.tinyscale.io/agent.md.
Use a new project directory and explicitly opt in; an existing agent credential
does not automatically acquire TinyImport permissions:

```sh
bun ./tinyscale-agent-v0.4.0.mjs workspace create --name "CSV import trial" --products tinyimport
bun ./tinyscale-agent-v0.4.0.mjs workspace status
bun ./tinyscale-agent-v0.4.0.mjs import configure --config import-schema.json
```

A minimal `import-schema.json` contains configuration, not customer records:

```json
{
  "name": "Contact identifiers",
  "allowedOrigins": ["https://your-owned-app.example"],
  "schema": {
    "version": "tinyimport.schema.v1",
    "revision": 1,
    "fields": [{"key":"reference","type":"text","required":true,"maxLength":80}]
  }
}
```

For a first test, make an explicitly synthetic CSV locally with a `Reference`
header and example identifiers such as `00123`. Do not read customer files into
model context. The fixed-purpose CLI reads one bounded regular file privately,
rejects symlinks/file changes, hashes it, and transfers bytes directly to the
allowlisted ingest endpoint. It prints only metadata and never upload tokens:

```sh
bun ./tinyscale-agent-v0.4.0.mjs import upload --schema SCHEMA_ID --revision 1 --file synthetic.csv --idempotency-key synthetic-upload-0001
bun ./tinyscale-agent-v0.4.0.mjs import map --import IMPORT_ID --mapping import-mapping.json
bun ./tinyscale-agent-v0.4.0.mjs import validate --import IMPORT_ID
bun ./tinyscale-agent-v0.4.0.mjs import status --import IMPORT_ID
bun ./tinyscale-agent-v0.4.0.mjs import binding --import IMPORT_ID
```

Use this caller-authored `import-mapping.json` for the synthetic example:

```json
{"version":"tinyimport.mapping.v1","revision":1,"columns":[{"source":"Reference","target":"reference"}]}
```

Reuse the upload idempotency key after a transport failure. Changed bytes with
that key conflict. Mapping changes need the next revision and invalidate prior
validation/approval. Poll status with bounded backoff while `validating`; an
accepted validation request is not a completed import. `ready` means all rows
validated; it does not authorize delivery. `needs_correction` requires a human to
inspect private errors and update the mapping or upload a corrected file.

The CLI keeps credentials under `.tinyscale`; never inspect, log or commit that
directory. It exposes no approval, private preview, session-token issuance or
customer batch-read command. A supported MCP client can discover limits,
configure schemas, submit caller-authored mappings, queue validation, read counts,
and inspect immutable digests. MCP never accepts CSV bytes or returns source
headers, customer rows, artifact URLs, upload tokens or backend capabilities.

## Human review and integrations

After showing the human the synthetic result, run:

```sh
bun ./tinyscale-agent-v0.4.0.mjs workspace claim
```

The private claim link opens in the human's browser without being printed. The
owner uses https://app.tinyscale.io/agent-workspaces to inspect private previews,
review bounded row-error metadata, and approve the exact validated revision.
Human claim preserves the installation IDs; it does not purchase a paid plan.
Only a wholly valid import can be approved. Changing source, mapping or schema
invalidates the relevant validation or capability. Cancellation cannot undo
writes the customer's backend already accepted.

The owner can create a backend capability for one schema and exact HTTPS origin.
The capability appears once in the protected owner view; install it through your
normal secret-management process. Never place it in frontend code, URLs, shell
arguments, models or logs. Only its SHA-256 digest is persisted. There are at most
five active capabilities per schema, with a lifetime of at most 30 days (bounded
by preview expiry). Revocation or a new current schema revision invalidates its
child sessions. To let an authenticated end user approve their own preview, the
owner must explicitly delegate that authority when creating the capability.

Your backend authenticates its application user and checks the user's permission
to import before requesting a short-lived browser session:

- POST https://ingest.tinyscale.io/v1/import/sessions/WORKSPACE_ID/SCHEMA_ID
- Bearer authorization uses the owner-created backend capability.
- Body: `{"version":"tinyimport.mint-session.v1"}`.
- Return the session response only to that authenticated browser with
  `Cache-Control: no-store`. Never log the response.

Sessions last at most 30 minutes, are restricted to the configured origin and
schema revision, and are capped at 20 active sessions per schema. After a session
expires, start a new session and import; a new session cannot take over the old
import. The owner can still review the original until its seven-day or earlier
workspace expiry. Each imported
file is bound to the session that created it. Another session cannot retrieve,
map or approve that file even on the same origin. A session does not grant the
right to approve unless its owner-created parent explicitly delegates it.

## Browser workflow

After your normal human approval for publication, load the v1 widget and your own
application bootstrap script. Allow the ingest origin in the relevant CSP rules.
The widget uses scoped styles, so the host CSP must allow its styles.

```html
<div id="csv-import"></div>
<script src="https://api.tinyscale.io/downloads/tinyimport-widget-v0.1.0.js" defer></script>
<script src="/csv-import.js" defer></script>
```

In the application's bootstrap, fetch a session from your authenticated backend,
then pass it directly to the widget in memory:

```js
const response = await fetch('/api/import-session', {method: 'POST'});
if (!response.ok) throw new Error('Import session unavailable');
const session = await response.json();
const widget = TinyImport.mount({
  host: document.getElementById('csv-import'),
  workspaceId: session.workspaceId,
  schemaId: session.schemaId,
  sessionToken: session.sessionToken,
  ingestOrigin: 'https://ingest.tinyscale.io',
  maxBytes: 1048576
});
```

Authenticate and apply your normal CSRF policy at `/api/import-session`; never
accept a caller-provided identity or approval flag. Set `maxBytes` from the
current allowance, up to 5 MiB. Call `widget.destroy()` when removing the view.
The user selects a file, maps columns, validates, reviews, and explicitly confirms
approval when delegated. Otherwise the owner approves in the TinyScale console.
The widget does not deliver rows to the customer's database.

## Customer-pulled delivery and retries

Your backend uses its capability to inspect imports for that schema and fetch
approved batches. These responses contain private customer data and are not
agent/model tools:

- GET `/v1/import/delivery/WORKSPACE_ID/SCHEMA_ID/imports`
- GET `/v1/import/delivery/WORKSPACE_ID/SCHEMA_ID/imports/IMPORT_ID`
- GET `/v1/import/delivery/WORKSPACE_ID/SCHEMA_ID/imports/IMPORT_ID/batches?index=0`
- POST `/v1/import/delivery/WORKSPACE_ID/SCHEMA_ID/imports/IMPORT_ID/ack`

Use the ingest origin for these paths. Follow `nextIndex` until it is null.
Each batch includes its immutable manifest, stable batch ID, and stable row IDs.
The batch and row identities bind the source/schema/mapping/output digests and
validation identity. A retry of the same validated partition has identical IDs.

In one customer-database transaction, deduplicate the batch ID (and row IDs as
needed), apply the entire batch, and record its accepted identity. Commit that
transaction before acknowledging TinyImport. A crash after commit but before
acknowledgment must return the stored acceptance on retry without applying the
writes again. If a batch cannot be accepted atomically, accept none and retry or
resolve the error; never acknowledge only a subset of its rows.

The acknowledgment body is:

```json
{
  "version":"tinyimport.acknowledge.v1",
  "importId":"IMPORT_UUID",
  "ack":{
    "version":"tinyimport.ack.v1",
    "batchId":"BATCH_SHA256",
    "status":"accepted",
    "acceptedRowIds":["EVERY_ROW_SHA256_IN_THIS_BATCH"]
  }
}
```

Duplicate complete acknowledgments are idempotent; changed or incomplete ones
conflict. This protocol helps the customer implement retry-safe processing. It
does not promise exactly-once external delivery or undo accepted writes.

## Limits, retention and failure behavior

Preview: 72 hours, two schemas, three imports, 1 MiB and 1,000 data rows per file.
After human claim, Free: five schemas, ten imports per UTC month, 5 MiB and 10,000
rows per file. At most one validation per installation runs concurrently.
Each import allows at most five validation requests; retries with the same
idempotency key do not consume another request. There are at most 100 columns, 16 KiB decoded bytes per field, 64 KiB per CSV
record, 100 rows/about 256 KiB per output batch, 256 output partitions and 16 MiB
cumulative artifact bytes per import across all validations and execution
retries. Reservations happen before storage writes; failed writes remain charged,
so changing mappings or retrying cannot reset the allowance. Expansion can hit the output
limit before the input-byte limit. These are hard stops; no automatic overage,
payment, or paid entitlement is created.

v1 supports UTF-8 CSV with quoted fields/newlines and text, integer, exact decimal,
explicit date formats and literal `true`/`false` booleans. Text preserves leading
zeros. Numeric output remains textual to avoid floating-point rounding. No XLSX,
archives, arbitrary JavaScript transforms, locale guessing or autonomous writes.

Raw files, previews and errors expire after seven days, or earlier when the
preview workspace expires. Expired, cancelled and erased imports are cleaned by
bounded tenant-checked artifact deletion; a seven-day bucket lifecycle backs up
late writes. Private data becomes unavailable at expiry even before a cleanup
pass finishes. Bounded configuration, usage counters, IDs, digests and receipts
can remain longer; erasure does not refund consumed allowance. Stop a customer's
backend delivery before erasing an import. Failures remain isolated from the
customer application's critical workflow.
