Skip to content

Classify SDK dependencies by their pubspec source, not a name list - #72

Merged
devin-ai-integration[bot] merged 19 commits into
milestone/stu-implement-updated-ui-uxfrom
fix/stu-368-sdk-source-classification
Sep 28, 2026
Merged

devin-ai-integration[bot] merged 19 commits into
milestone/stu-implement-updated-ui-uxfrom
fix/stu-368-sdk-source-classification

Conversation

@sgardoll

@sgardoll sgardoll commented Sep 21, 2026 •

Copy link
Copy Markdown
Owner

Closes STU-368. Ends the trade the a1238f0 revert re-opened: after this, an unreproducible dependency no longer refuses the deploy — the classes it can affect are reported unverified and the rest still compile for real.

The bug

Which packages the Flutter SDK supplies was decided by a hand-maintained name list kept in three places. flutter_web_plugins was in none of them, so every project declaring it — every web-enabled project with a plugin — read it as a dependency that could not be reproduced, and the deploy was refused for a package most of its classes never imported.

Changes

The pubspec already held the answer. The parser read each block-form dependency's own keys and threw them away. It now returns them as sourceKey/sourceValue:

First own-key Meaning
sdk: Flutter SDK — reproduced from the name alone
a nested version: a constraint written on its own line
git: / path: / nested hosted: not expressible in the manifest

SDK_DEPENDENCIES is deleted. Nothing classifies by package name any more, so the next SDK package Flutter ships needs no change here.

The runner's allowedSdkPackages is deleted too. It independently refused the same deploy with Unsupported SDK package: .... One safety note: that allowlist was also the only validation of an sdkPackages entry, so the package-name pattern now does that job. Without the swap, a caller-supplied name would reach the generated pubspec unchecked — a YAML-injection path. The pattern was already there and already the guard on dependency keys; it is now the guard here too. The runner still writes sdk: flutter itself (hardcoded), so that value never comes from a caller.

Skip is scoped by reachability — at the graph level, not the import level. A class naming an unreproducible entry directly gets the precise "it imports X" reason. But review rounds established that per-import scoping is unsound: a git/path dep's own pubspec still constrains every package it shares with a class's imports, and a dependency_overrides entry rewrites resolution for every dependent — so any unreproducible entry skips every class importing a package: URI. dart:-only classes resolve nothing through pub and still verify. Which packages drift is only recorded in the project's pubspec.lock, never pushed, so no narrower rule can be trusted.

Unchanged, deliberately: the accept/warn split already worked. A real analyzer error refuses with HTTP 422; a class that could not be compiled is pushed with its warning logged before the provisioning request. STU-368's original acceptance text said such a class "is refused" — that described the a1238f0 behaviour we reverted, so the issue is updated to match the agreed policy rather than the code being bent back to it.

Doc fix: DEPLOYMENT.md still described the scaffolding-closure compilation that reverting a1238f0 removed, and still claimed "nothing unverified is deployed". Corrected.

Review follow-ups

Five review rounds (Devin Review + Greptile) landed further fixes on top of the classification change:

  • readSourceDirective read only the first own-key, but YAML mapping order is not significant — version: written above hosted: classified a custom-hosted package as a pub.dev constraint. Every own-key is now read and a source key wins over version:.
  • extractPackageImports/extractImportUris matched only the first URI of an import, missing export directives and if (...) conditional alternatives. Both now scan the whole directive to its ; and read every URI literal.
  • Directive/URI parsing was further hardened twice: ; inside a quoted URI (src/a;b.dart) and escaped quotes ('a\'b.dart', "it's.dart") each truncated or dropped the directive — losing the URI and sending an unverifiable class to the analyzer as a missing-file error. Literals are now stepped over whole, escape-aware, and each quote type only forbids its own delimiter.
  • The runner's sdkPackages list was unbounded once the allowlist went away. It is now capped at maxDependenciesPerRequest, names are limited to maxPackageNameLength, and dedup uses a set.
  • Inline flow mappings (name: {sdk: flutter}) read as scalar constraints and were forwarded to the runner — whose constraint validator rejects braces — refusing the whole deploy. parseFlowSourceDirective reads their top-level keys with the same source-over-version: precedence, and an unreadable mapping is unrepresentable rather than a constraint.
  • sdk:-sourced overrides merged into sdkPackages and were emitted under dependencies: — colliding with a same-named scalar dep as a duplicate YAML key. They now ride a separate sdkOverrides channel and are emitted under dependency_overrides:. (This also surfaced that scalar overrides were sent as overrides while the runner read dependencyOverrides — the field name now matches the wire contract.)
  • extractPackageImports kept filtering SDK names (flutter, flutter_localizations, ...) even in verification — so a project forking an SDK package from git hid it from the unreproducible check. The extractor now returns SDK names to every caller; a name absent from the project's pubspec becomes an {sdk: flutter} addition rather than a hosted constraint, so an undeclared flutter_web_plugins import still deploys.

⚠️ Deployment order matters

server.dart changed, so the runner must go first. The current live runner still enforces the old allowlist, and this client will send it package names that allowlist rejects — deploying the web app alone would re-break exactly what we just fixed. The new runner is backward compatible with the old client's narrower list, so runner-first is safe.

Verified

Gate Result
npm test 291/291 (+20 new)
npm run test:deploy-guard 9/9
npm run test:proxies 30/30
npm run build clean
dart analyze (runner) No issues found

New tests cover: flutter_web_plugins reproduced with no list (block and inline flow form); a git: dependency's transitive-constraint poison skipping every package-importing class while dart:-only classes still verify; the reason naming a class's own direct import; a nested version: read as a constraint; a class importing an SDK package not being flagged as "missing"; the parser boundary case where a following sibling's key must not be read as the previous entry's source; an export of an unreproducible package being skipped; a version:-before-hosted: entry still classified hosted; an unreproducible override skipping all package-importing classes; a conditional if (...) import of an unreproducible dependency; ;-in-URI and escaped-quote and opposite-quote URIs surviving extraction; an sdk: override riding its own channel; a git-forked SDK name caught by verification; and inline {sdk:}/{git:}/{version:}/unreadable flow mappings classified by their real source.

Link to Devin session: https://app.devin.ai/sessions/616e9d5ba84144cfb7168de3eed8da26
Open in Devin Desktop: https://app.devin.ai/desktop/session/616e9d5ba84144cfb7168de3eed8da26?variant=devin
Requested by: @sgardoll


Devin Review

@sgardoll

Copy link
Copy Markdown
Owner Author

@greptileai review

greptile-apps[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

…nd sdkPackages

Review follow-ups on STU-368.

readSourceDirective returned the first own-key, but YAML mapping order is
not significant: 'version:' written above 'hosted:' read as a plain
constraint and the manifest resolved the package from pub.dev rather than
the project's host. Every own-key is now read and any source key wins over
'version:'.

extractPackageImports matched only 'import'. A class exporting a git or
path package was compiled against a manifest missing it and the analyzer
refused the deploy over an unresolvable URI, instead of the class being
skipped with a warning. 'export' directives now count.

The runner's sdkPackages array had no count bound after the allowlist was
removed; it now shares maxDependenciesPerRequest, and package names in
every manifest field are capped at pub's 64-character limit.

Co-Authored-By: bot_apk <apk@cognition.ai>
@devin-ai-integration

Copy link
Copy Markdown
Contributor

@greptileai review

Review follow-up on STU-368.

Per-class scoping assumed an unreproducible package only affects classes
that name it directly. That holds for a git/path entry in dependencies -
a published package cannot depend on an unpublished one - but not for
dependency_overrides: an override rewrites resolution for every package
in the graph that depends on the overridden name. A class importing an
ordinary pub.dev package could then be analysed against different
transitive code than the project compiles, and only pubspec.lock - which
a deploy never sees - records whether it does.

Unrepresentable overrides now skip any class that imports a package:
URI at all; dart:-only classes still verify. Regular unreproducible
dependencies keep their per-class scoping.

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Review follow-ups on STU-368.

A dependency written as a flow mapping - 'flutter_web_plugins: {sdk:
flutter}' - has a nonempty value, so it read as a scalar constraint and
the braces were forwarded to the runner, which rejects them and refuses
the whole deploy. The parser now reads a flow mapping's own keys with the
same source-over-version precedence as block form, and an unreadable
mapping is unrepresentable rather than a constraint.

Import/export directives can select a different file per platform with
'if (dart.library.x)': every URI literal in the directive is code the
class needs. The package scan now reads them all, so a conditional
import of an unreproducible dependency leaves the class uncompiled and
reported instead of refused on a missing URI.

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor

@greptileai review

@greptile-apps

greptile-apps Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

[Medium risk] Changes how SDK dependencies are classified in verification.

The PR is not yet safe to merge because a valid semicolon-containing import URI can turn an unverifiable class into a refused deploy.

Fix All in DevinFindings

  1. P1 Semicolon truncates import URI ▶
Fix with agent prompt
### Issue 1
src/dartPackageImports.js:63
A Dart import URI can contain a semicolon, but this pattern stops at the first `;`, before the URI’s closing quote. For a class importing a project-relative file such as `src/a;b.dart`, `extractImportUris` drops the URI. The planner then sends the class for analysis without that file, and a missing-URI error can refuse the deploy instead of reporting the class as unverified. Greptile automatically discovered a related ticket stating that classes which cannot be verified should be reported and allowed to deploy, which informed this comment.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

The PR replaces SDK package-name allowlists with pubspec-source classification, scopes unverifiable direct dependencies to affected classes, and updates the runner’s manifest validation and deployment documentation.

  • The follow-up adds source-key precedence and inline mapping support, recognizes exports and conditional import URIs, and bounds SDK package input.
  • Greptile automatically discovered a related ticket that helped explain the purpose of this PR: verify classes unaffected by unreproducible dependencies while reporting, rather than refusing, classes that cannot be checked.

Reviews (2) · Last reviewed commit: "fix: read inline dependency mappings and..."

greptile-apps[bot]

This comment was marked as resolved.

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

… correctly

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…cation

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor

E2E deploy-pipeline verification — drove the real app UI (npm run dev, ?debugBundle=multi fixture with 5 crafted classes) through the full deploy flow twice, against a local stub for the FlutterFlow API + provision runner (no real FF credentials exist — those remote edges are the only simulated layer; all changed client code ran for real, incl. live pub.dev resolution).

Run A — unreproducible dependency_overrides ({path: ...}):
run A success modal: 4 unverified warnings

Exactly 4 "Not verified before deploying" warnings with correct discriminating reasons:

  • HttpUser — skipped by the override-poison rule (dependency_overrides redirects override_me)
  • ConditionalUser — imports forked_pkg, only reachable via the if (dart.library.io) second URI in its conditional import → multi-URI extraction works
  • SemiImport — reason quotes src/data;store.dart verbatim → the ;-inside-URI fix works
  • EscapeImport — reason quotes src/it\'s.dart verbatim → the escaped-quote fix works
  • AgentEvent (dart-only) absent from warnings → still verified

Run B — representable override (^1.3.0), same classes:
run B success modal: 3 warnings

HttpUser now verified — the A→B flip proves the override rule is scoped to unreproducible sources only.

Captured payloads (details)

Run A deployCustomClasses body: verification.sources=[agent_event.dart] only, sdkPackages=[flutter, flutter_localizations] (inline {sdk: flutter} parsed as SDK source — not emitted as a bogus constraint), dependencies={http:^1.2.0, collection:^1.18.0} plain constraints, dependencyOverrides={} (path override excluded). Run B: sources=[agent_event, http_user], dependencyOverrides={override_me:'^1.3.0'}. Push body's serialized_yaml preserves {sdk: flutter}/{git:...} flow mappings verbatim and appends the added collection: ^1.18.0. A scratch package rebuilt from run B's manifest + sources passes flutter pub get + dart analyze clean.

Not exercised: real api.flutterflow.io push and the real Cloud Run compile runner (no credentials). Written by Devin

devin-ai-integration[bot]

This comment was marked as resolved.

…n shift the graph

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…arison literals in conditions

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>
github-advanced-security[bot]

This comment was marked as resolved.

…r raw prefix

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…version scalars

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

…lose

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 2 commits September 28, 2026 09:36
…ontent

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>
…ed member rewrite as skipped

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…n't match

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor

Duplicate of the resolved thread — fixed in 43a1a02: entries record the name line as nameIndex, so rewriteVersionMember reaches multiline flow members; end-to-end test covers an incompatible multiline {version:} pin.

…ui-ux' into fix/stu-368-sdk-source-classification

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>

# Conflicts:
#	dist/index.html
#	src/customCodeVerification.js
#	src/customCodeVerification.test.js
#	src/pubspecSync.js
@devin-ai-integration
devin-ai-integration Bot changed the base branch from main to milestone/stu-implement-updated-ui-ux September 28, 2026 15:43
@devin-ai-integration
devin-ai-integration Bot merged commit b69afe7 into milestone/stu-implement-updated-ui-ux Sep 28, 2026
5 checks passed

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 new potential issues.

Devin Review

Comment thread src/dartPackageImports.js Resolved
Comment thread src/pubspecSync.js Resolved
Comment thread DEPLOYMENT.md Resolved
Comment thread cloud-run/ffai-runner/bin/server.dart Resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants