HarperFast / HarperFast/harper

Resource get()-derived attribute silently persists as a stored column on round-trip GET→PUT

Open
#1,400 0 comments 0 reactions 0 assignees View on GitHub
area:rest-api bug
Dominant language
JavaScript
Stars
89
Forks
10
Avg merge
2d 6h
Merged PRs (30d)
200

Description

## Summary

A derived attribute added **only in a custom Resource's `get()` override** (not declared in the schema) is included in the GET response, and on the common **fetch → mutate → save** round-trip it gets written straight into storage as a real stored column — silently polluting the persisted record.

By contrast, a **schema `@computed(from:)` field is guarded** — a PUT carrying it is rejected (`400 "Computed property may not be directly assigned"`, `resources/Table.ts:3496`) and not persisted. The gap is specifically the resource-layer `get()`-added field, which has no such guard.

## Severity

Medium — silent data pollution on a very common client idiom (read an object, change one field, PUT it back) for any app that derives a view field in a resource `get()`. No crash; the record gains an undeclared stored attribute that then also shadows the derived value on subsequent reads.

## Repro

1. Define a custom Resource over a table whose `get()` adds a non-schema field, e.g. `record.fullName = record.first + ' ' + record.last`.
2. `GET /Person/p1` → response includes `fullName`.
3. Take that response object, change `first`, and `PUT /Person/p1` with the whole object back.
4. `GET /Person/p1` (or inspect storage) → `fullName` is now a **stored** column on the record (it was persisted), not re-derived.

Contrast control: a schema `@computed` field in the same PUT body is rejected with 400 and not stored.

## Recommendation

Guard resource-`get()`-derived fields on write the same way schema `@computed` is — reject (or drop) them on PUT — or document that resource-layer derived fields must be stripped before a write-back.

## Companion observations (computed-field surface, lower priority — D-062)

- REST GET-by-id **omits** computed fields (returns stored columns only, `Table.ts:2566`) while SQL `SELECT *` **includes** them — same record, different surfaces.
- REST `sort(computedField)` alone → 404 "not indexed"; `sort(computed)` + a filter → silently returns `[]` (SQL `ORDER BY computed` works). Filtering *by* a computed field works correctly on all surfaces.

---
*Surfaced by the QA-explorer campaign against Harper `7aaa5a152`. The resource get/merge code path is not changed vs `main` @`6797f091d` (the only feature-branch delta is an unrelated revalidation line). Ready-to-promote test exists. Filed by Claude (Opus 4.8) for @kris.*

Contributor guide

Open the contributing guide

Research direction

Start with the resource get/merge write path and compare it with the computed-field guard at resources/Table.ts:3496; the issue also identifies REST GET handling at Table.ts:2566. Run the ready-to-promote test mentioned in the report, then verify that a GET→PUT round-trip does not persist the resource-derived field and that the existing computed-field behavior remains unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
api, backend, database
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.