docs(openspec): move-image-cropper change — specs synced, change archived
image-cropper capability added; tools-app landing + config requirements updated (internal /cropper link, credentials-via-env carve-out).
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
# Spec Delta: image-cropper
|
||||
|
||||
## Purpose
|
||||
|
||||
The Image Cropper at `/cropper`: operators review, crop, clean and save
|
||||
identity photos submitted for YOUreka ID domain profiles, with every proxied
|
||||
action authorized by the crop_image relation.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Crop permission gate
|
||||
Access to the cropper SHALL require the `crop_image` relation on `domain:idid`
|
||||
(subsystem `idid`) for the signed-in operator's phone, verified against the
|
||||
portal OpenFGA endpoint and failing closed on denial or error. Read paths
|
||||
(page load, picture loading, background cleanup, dequeue) MAY serve a cached
|
||||
result for up to 5 minutes; saving SHALL always verify live.
|
||||
|
||||
#### Scenario: Permitted operator opens the cropper
|
||||
- **WHEN** an operator with the crop_image relation requests `/cropper`
|
||||
- **THEN** the editor page loads
|
||||
|
||||
#### Scenario: Non-permitted operator is kept out
|
||||
- **WHEN** an operator without the crop_image relation requests the cropper
|
||||
page
|
||||
- **THEN** the page renders a not-permitted explanation with no editor UI
|
||||
|
||||
#### Scenario: Cached gate on reads
|
||||
- **WHEN** a read action occurs within 5 minutes of the operator's last
|
||||
permission check
|
||||
- **THEN** the cached result is used without calling the portal
|
||||
|
||||
### Requirement: Picture loading is gated and namespaced
|
||||
The cropper SHALL load review pictures only through its namespaced routes
|
||||
(`/cropper/picture/next`, `/cropper/picture/load/:id`,
|
||||
`/cropper/picture/:file_name`) behind the session and crop gates. A denied
|
||||
route SHALL return 403 without contacting the queue, storage, or review
|
||||
services.
|
||||
|
||||
#### Scenario: Next queued picture
|
||||
- **WHEN** the operator requests the next picture
|
||||
- **THEN** the route proxies the Azure queue and returns the queue message
|
||||
|
||||
#### Scenario: Denied picture load does not reach services
|
||||
- **WHEN** a request without the crop_image relation loads a picture route
|
||||
- **THEN** the route answers 403 and makes no upstream calls
|
||||
|
||||
### Requirement: Background cleanup is gated
|
||||
The background-cleanup proxy (`/cropper/api/removebg`) SHALL require the
|
||||
session and crop gate, forwarding the cropped image to the cleanup service
|
||||
only for permitted operators.
|
||||
|
||||
#### Scenario: Denied cleanup does not reach the cleanup service
|
||||
- **WHEN** a request without the crop_image relation calls the cleanup proxy
|
||||
- **THEN** it answers 403 without contacting the cleanup service
|
||||
|
||||
#### Scenario: Cleanup proxies the cleaned image
|
||||
- **WHEN** a permitted operator submits a cropped image for cleanup
|
||||
- **THEN** the route forwards it to the cleanup service and returns the
|
||||
cleaned image
|
||||
|
||||
### Requirement: Saving requires a live gate and an OTP
|
||||
Saving a cropped picture (`/cropper/profile/picture`) SHALL verify the crop
|
||||
permission live (bypassing the cache) and SHALL require an OTP from the
|
||||
operator, forwarding the session phone as the validating identity together
|
||||
with the OTP and the picture's domain to a2loft. A denied or OTP-less save
|
||||
SHALL not reach a2loft.
|
||||
|
||||
#### Scenario: Successful save
|
||||
- **WHEN** a permitted operator saves with an OTP
|
||||
- **THEN** the route verifies the live gate and forwards the picture to
|
||||
a2loft with the session phone, OTP, and picture domain
|
||||
|
||||
#### Scenario: Denied save does not reach a2loft
|
||||
- **WHEN** the live crop check denies, or the OTP is missing
|
||||
- **THEN** the route answers 403/400 and makes no a2loft call
|
||||
|
||||
### Requirement: Unified login — no separate artist login
|
||||
The cropper SHALL NOT implement its own login. The operator's identity comes
|
||||
from the YOUreka session (phone prefilled), the picture's metadata carries the
|
||||
domain, and the only credential the operator supplies is the save-time OTP.
|
||||
|
||||
#### Scenario: Phone prefilled from the session
|
||||
- **WHEN** a signed-in operator opens the cropper
|
||||
- **THEN** their phone is already known to the page without any login form
|
||||
|
||||
### Requirement: Cropper credentials come from the environment
|
||||
The cropper's service credentials (Minio keys, Azure storage connection
|
||||
string, nodered credentials, cleanup-service key) SHALL be read from
|
||||
environment variables. When they are absent, the cropper routes SHALL answer
|
||||
503 without attempting upstream calls; the application itself SHALL still
|
||||
boot.
|
||||
|
||||
#### Scenario: Unconfigured deployment degrades gracefully
|
||||
- **WHEN** the app starts without the cropper credentials
|
||||
- **THEN** the other tools work and cropper routes answer 503
|
||||
@@ -0,0 +1,30 @@
|
||||
# Spec Delta: tools-app
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Landing page links the YOUreka tools
|
||||
The app SHALL serve a landing page at `/` containing links to the Image
|
||||
Cropper (`/cropper`) and to the Fingerprint Agent page (`/fingerprint`).
|
||||
|
||||
#### Scenario: Landing page renders both tool links
|
||||
- **WHEN** a user opens the app root URL
|
||||
- **THEN** the page shows a link to the Image Cropper pointing at
|
||||
`/cropper` and a link to `/fingerprint`
|
||||
|
||||
### Requirement: Configuration is hardcoded
|
||||
The app SHALL hardcode all configuration in source: port `4173`, portal
|
||||
`https://portal.yrka.app`, PocketBase `https://pb.itcass.net`, a2loft
|
||||
`https://apis.a2loft.net`, and agent `http://127.0.0.1:9000`. There SHALL be
|
||||
no runtime configuration via environment variables or config files, with one
|
||||
exception: the Image Cropper's service credentials (Minio keys, Azure storage
|
||||
connection string, nodered credentials, cleanup-service key) SHALL come from
|
||||
environment variables and SHALL NOT be stored in source.
|
||||
|
||||
#### Scenario: No configuration required to run
|
||||
- **WHEN** the app is started with no environment variables or config files
|
||||
- **THEN** it serves on port 4173 with the hardcoded external endpoints
|
||||
|
||||
#### Scenario: Cropper credentials from the environment
|
||||
- **WHEN** the cropper credentials are supplied as environment variables
|
||||
- **THEN** the cropper routes use them without any change to the hardcoded
|
||||
URL/port configuration
|
||||
Reference in New Issue
Block a user